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 8fd3e7cff..07fb6b68b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,7 +26,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 +70,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
@@ -141,4 +142,4 @@ gem 'doorkeeper'
gem 'doorkeeper-jwt'
-gem 'gitea-client', '~> 1.4.3'
+gem 'gitea-client', '~> 1.5.7'
diff --git a/Gemfile.lock b/Gemfile.lock
index b7f060b2a..fd4d95fa4 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:
@@ -99,6 +107,7 @@ GEM
archive-zip (~> 0.10)
nokogiri (~> 1.8)
chunky_png (1.3.11)
+ coderay (1.1.3)
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
crass (1.0.6)
@@ -135,7 +144,7 @@ GEM
fugit (1.4.1)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.4)
- gitea-client (1.4.2)
+ gitea-client (1.5.7)
rest-client (~> 2.1.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
@@ -150,6 +159,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 +199,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
@@ -244,14 +256,18 @@ GEM
popper_js (1.16.0)
powerpack (0.1.2)
prettier (0.18.2)
+ pry (0.12.2)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry-rails (0.3.9)
+ pry (>= 0.10.4)
public_suffix (4.0.3)
- puma (3.12.2)
+ puma (5.6.8)
+ nio4r (~> 2.0)
raabro (1.4.0)
rack (2.0.9)
rack-cors (1.1.1)
rack (>= 2.0.0)
- rack-mini-profiler (2.0.1)
- rack (>= 1.2.0)
rack-protection (2.0.8.1)
rack
rack-test (1.1.0)
@@ -438,6 +454,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)
@@ -450,7 +468,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)
@@ -492,7 +510,7 @@ DEPENDENCIES
enumerize
faraday (~> 0.15.4)
font-awesome-sass (= 4.7.0)
- gitea-client (~> 1.4.2)
+ gitea-client (~> 1.5.7)
grape-entity (~> 0.7.1)
groupdate (~> 4.1.0)
harmonious_dictionary (~> 0.0.1)
@@ -514,9 +532,9 @@ DEPENDENCIES
parallel (~> 1.19, >= 1.19.1)
pdfkit
prettier
- puma (~> 3.11)
+ pry-rails
+ puma (~> 5.6.5)
rack-cors
- rack-mini-profiler
rails (~> 5.2.0)
rails-i18n (~> 5.1)
ransack
@@ -538,9 +556,10 @@ DEPENDENCIES
sidekiq-cron (= 1.2.0)
sidekiq-failures
simple_form
- simple_xlsx_reader
+ 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/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/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/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/action/node_inputs_controller.rb b/app/controllers/action/node_inputs_controller.rb
new file mode 100644
index 000000000..65227c657
--- /dev/null
+++ b/app/controllers/action/node_inputs_controller.rb
@@ -0,0 +1,75 @@
+class Action::NodeInputsController < ApplicationController
+ before_action :require_admin, except: [:index]
+ before_action :find_action_node
+
+ def index
+ @node_inputs = @node.action_node_inputs
+ respond_to do |format|
+ format.html
+ format.json
+ end
+ end
+
+ def create
+ @node_input = Action::NodeInput.new(node_input_params)
+ @node_input.action_node = @node
+ respond_to do |format|
+ if @node_input.save
+ format.html { redirect_to action_node_node_inputs_path(@node), notice: '创建成功.' }
+ format.json { render_ok(data: @node_input.as_json) }
+ else
+ format.html { render :new }
+ format.json { render json: @node_input.errors, status: -1 }
+ end
+ end
+ end
+
+ def new
+
+ end
+
+ def show
+
+ end
+
+ def edit
+
+ end
+
+ def update
+ @node_input.update(node_input_params)
+ respond_to do |format|
+ format.html { redirect_to action_node_node_inputs_path(@node), notice: '更新成功.' }
+ format.json { render_ok(data: @node_input.as_json) }
+ end
+ end
+
+ def destroy
+ if @node_input.destroy!
+ flash[:success] = '删除成功'
+ else
+ flash[:danger] = '删除失败'
+ end
+ redirect_to "api/actions/nodes"
+ end
+
+ private
+
+ def find_action_node
+ @node = Action::Node.find(params[:node_id])
+ if params[:id].present?
+ @node_input = @node.action_node_inputs.find(params[:id])
+ else
+ @node_input = Action::NodeInput.new
+ end
+
+ end
+
+ def node_input_params
+ if params.require(:action_node_input)
+ params.require(:action_node_input).permit(:name, :input_type, :description, :is_required, :sort_no)
+ else
+ params.permit(:name, :input_type, :description, :is_required, :sort_no)
+ end
+ end
+end
diff --git a/app/controllers/action/node_selects_controller.rb b/app/controllers/action/node_selects_controller.rb
new file mode 100644
index 000000000..9acd6fc5f
--- /dev/null
+++ b/app/controllers/action/node_selects_controller.rb
@@ -0,0 +1,76 @@
+class Action::NodeSelectsController < ApplicationController
+
+ before_action :require_admin, except: [:index]
+ before_action :find_action_node
+
+ def index
+ @node_selects = @node.action_node_selects
+ respond_to do |format|
+ format.html
+ format.json
+ end
+ end
+
+ def create
+ @node_select = Action::NodeSelect.new(node_select_params)
+ @node_select.action_node = @node
+ respond_to do |format|
+ if @node_select.save
+ format.html { redirect_to action_node_node_selects_path(@node), notice: '创建成功.' }
+ format.json { render_ok(data: @node_select.as_json) }
+ else
+ format.html { render :new }
+ format.json { render json: @node_select.errors, status: -1 }
+ end
+ end
+ end
+
+ def new
+
+ end
+
+ def show
+
+ end
+
+ def edit
+
+ end
+
+ def update
+ @node_select.update(node_select_params)
+ respond_to do |format|
+ format.html { redirect_to action_node_node_selects_path(@node), notice: '更新成功.' }
+ format.json { render_ok(data: @node_select.as_json) }
+ end
+ end
+
+ def destroy
+ if @node_select.destroy!
+ flash[:success] = '删除成功'
+ else
+ flash[:danger] = '删除失败'
+ end
+ redirect_to "api/actions/nodes"
+ end
+
+ private
+
+ def find_action_node
+ @node = Action::Node.find(params[:node_id])
+ if params[:id].present?
+ @node_select = @node.action_node_selects.find(params[:id])
+ else
+ @node_select = Action::NodeSelect.new
+ end
+
+ end
+
+ def node_select_params
+ if params.require(:action_node_select)
+ params.require(:action_node_select).permit(:name, :val, :val_ext, :description, :sort_no)
+ else
+ params.permit(:name, :val, :val_ext, :description, :sort_no)
+ end
+ end
+end
diff --git a/app/controllers/action/node_types_controller.rb b/app/controllers/action/node_types_controller.rb
new file mode 100644
index 000000000..32508d942
--- /dev/null
+++ b/app/controllers/action/node_types_controller.rb
@@ -0,0 +1,64 @@
+class Action::NodeTypesController < ApplicationController
+ before_action :require_admin, except: [:index]
+ before_action :find_node_type, except: [:index, :create, :new]
+
+ def index
+ @node_types = Action::NodeType.all
+ end
+
+ def create
+ @node_type = Action::NodeType.new(node_types_params)
+ respond_to do |format|
+ if @node_type.save
+ format.html { redirect_to action_node_types_path, notice: '创建成功.' }
+ format.json { render_ok(data: @node_type.as_json) }
+ else
+ format.html { render :new }
+ format.json { render json: @node_type.errors, status: -1 }
+ end
+ end
+ end
+
+ def show
+
+ end
+
+ def new
+ @node_type = Action::NodeType.new
+ end
+
+ def edit
+
+ end
+
+ def update
+ @node_type.update(node_types_params)
+ respond_to do |format|
+ format.html { redirect_to action_node_types_path, notice: '更新成功.' }
+ format.json { render_ok(data: @node_type.as_json) }
+ end
+ end
+
+ def destroy
+ if @node_type.destroy!
+ flash[:success] = '删除成功'
+ else
+ flash[:danger] = '删除失败'
+ end
+ redirect_to action_node_types_path
+ end
+
+ private
+
+ def find_node_type
+ @node_type = Action::NodeType.find(params[:id])
+ end
+
+ def node_types_params
+ if params.require(:action_node_type)
+ params.require(:action_node_type).permit(:name, :description, :sort_no)
+ else
+ params.permit(:name, :description, :sort_no)
+ end
+ end
+end
diff --git a/app/controllers/action/nodes_controller.rb b/app/controllers/action/nodes_controller.rb
new file mode 100644
index 000000000..e1e7799f4
--- /dev/null
+++ b/app/controllers/action/nodes_controller.rb
@@ -0,0 +1,69 @@
+class Action::NodesController < ApplicationController
+ before_action :require_admin, except: [:index]
+ before_action :find_action_node, except: [:index, :create, :new]
+
+ def index
+ @node_types = Action::NodeType.all
+ @no_type_nodes = Action::Node.where(action_node_types_id: nil)
+ respond_to do |format|
+ format.html { @nodes = Action::Node.all }
+ format.json
+ end
+ end
+
+ def create
+ @node = Action::Node.new(node_params)
+ respond_to do |format|
+ if @node.save
+ format.html { redirect_to action_nodes_path, notice: '创建成功.' }
+ format.json { render_ok(data: @node.as_json) }
+ else
+ format.html { render :new }
+ format.json { render json: @node.errors, status: -1 }
+ end
+ end
+ end
+
+ def new
+ @node = Action::Node.new
+ end
+
+ def show
+
+ end
+
+ def edit
+
+ end
+
+ def update
+ @node.update(node_params)
+ respond_to do |format|
+ format.html { redirect_to action_nodes_path, notice: '更新成功.' }
+ format.json { render_ok(data: @node.as_json) }
+ end
+ end
+
+ def destroy
+ if @node.destroy!
+ flash[:success] = '删除成功'
+ else
+ flash[:danger] = '删除失败'
+ end
+ redirect_to action_nodes_path
+ end
+
+ private
+
+ def find_action_node
+ @node = Action::Node.find(params[:id])
+ end
+
+ 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)
+ else
+ params.permit(:name, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
+ end
+ end
+end
diff --git a/app/controllers/action/templates_controller.rb b/app/controllers/action/templates_controller.rb
new file mode 100644
index 000000000..092d38d64
--- /dev/null
+++ b/app/controllers/action/templates_controller.rb
@@ -0,0 +1,68 @@
+class Action::TemplatesController < ApplicationController
+ before_action :require_admin, except: [:index]
+ before_action :find_action_template, except: [:index, :create, :new]
+
+ def index
+ @templates = Action::Template.all
+ respond_to do |format|
+ format.html
+ format.json
+ end
+ end
+
+ def create
+ @template = Action::Template.new(templates_params)
+ respond_to do |format|
+ if @template.save
+ format.html { redirect_to action_templates_path, notice: '创建成功.' }
+ format.json { render_ok(data: @template.as_json) }
+ else
+ format.html { render :new }
+ format.json { render json: @template.errors, status: -1 }
+ end
+ end
+ end
+
+ def show
+
+ end
+
+ def new
+ @template = Action::Template.new
+ end
+
+ def edit
+
+ end
+
+ def update
+ @template.update(templates_params)
+ respond_to do |format|
+ format.html { redirect_to action_templates_path, notice: '更新成功.' }
+ format.json { render_ok(data: @template.as_json) }
+ end
+ end
+
+ def destroy
+ if @template.destroy!
+ flash[:success] = '删除成功'
+ else
+ flash[:danger] = '删除失败'
+ end
+ redirect_to action_templates_path
+ end
+
+ private
+
+ def find_action_template
+ @template = Action::Template.find(params[:id])
+ end
+
+ def templates_params
+ if params.require(:action_template)
+ params.require(:action_template).permit(:name, :description, :img, :sort_no, :json, :yaml)
+ else
+ params.permit(:name, :description, :img, :sort_no, :json, :yaml)
+ end
+ end
+end
diff --git a/app/controllers/admins/dashboards_controller.rb b/app/controllers/admins/dashboards_controller.rb
index 2c01c8bd0..37b69dc0d 100644
--- a/app/controllers/admins/dashboards_controller.rb
+++ b/app/controllers/admins/dashboards_controller.rb
@@ -21,13 +21,75 @@ class Admins::DashboardsController < Admins::BaseController
weekly_project_ids = (CommitLog.where(created_at: current_week).pluck(:project_id).uniq + Issue.where(created_on: current_week).pluck(:project_id).uniq).uniq
month_project_ids = (CommitLog.where(created_at: current_month).pluck(:project_id).uniq + Issue.where(created_on: current_month).pluck(:project_id).uniq).uniq
@day_active_project_count = Project.where(updated_on: today).or(Project.where(id: day_project_ids)).count
- @weekly_active_project_count = Project.where(updated_on: current_week).or(Project.where(id: weekly_project_ids)).count
- @month_active_project_count = Project.where(updated_on: current_month).or(Project.where(id: month_project_ids)).count
-
+ @weekly_active_project_count = Rails.cache.fetch("dashboardscontroller:weekly_active_project_count", expires_in: 10.minutes) do
+ Project.where(updated_on: current_week).or(Project.where(id: weekly_project_ids)).count
+ end
+ @month_active_project_count = Rails.cache.fetch("dashboardscontroller:month_active_project_count", expires_in: 1.hours) do
+ Project.where(updated_on: current_month).or(Project.where(id: month_project_ids)).count
+ end
# 新增项目数
- @day_new_project_count = Project.where(created_on: today).count
- @weekly_new_project_count = Project.where(created_on: current_week).count
- @month_new_project_count = Project.where(created_on: current_month).count
+ @day_new_project_count = Rails.cache.fetch("dashboardscontroller:day_new_project_count", expires_in: 10.minutes) do
+ Project.where(created_on: today).count
+ end
+ @weekly_new_project_count = Rails.cache.fetch("dashboardscontroller:weekly_new_project_count", expires_in: 10.minutes) do
+ Project.where(created_on: current_week).count
+ end
+ @month_new_project_count = Rails.cache.fetch("dashboardscontroller:month_new_project_count", expires_in: 1.hours) do
+ Project.where(created_on: current_month).count
+ end
+
+
+ # 总的平台用户数
+ # 总的平台项目数
+ # 总的平台组织数
+ # 总的平台Issue数、评论数、PR数、Commit数
+ @user_count = Rails.cache.fetch("dashboardscontroller:platform:user_count", expires_in: 1.days) do
+ User.count
+ end
+ @project_count = Rails.cache.fetch("dashboardscontroller:platform:project_count", expires_in: 1.days) do
+ Project.count
+ end
+ @organization_count = Rails.cache.fetch("dashboardscontroller:platform:organization_count", expires_in: 1.days) do
+ Organization.count
+ end
+ @issue_count = Rails.cache.fetch("dashboardscontroller:platform:issue_count", expires_in: 1.days) do
+ Issue.count
+ end
+ @comment_count = Rails.cache.fetch("dashboardscontroller:platform:comment_count", expires_in: 1.days) do
+ Journal.count
+ end
+ @pr_count = Rails.cache.fetch("dashboardscontroller:platform:pr_count", expires_in: 1.days) do
+ PullRequest.count
+ end
+ @commit_count = Rails.cache.fetch("dashboardscontroller:platform:commit_count", expires_in: 1.days) do
+ CommitLog.count
+ end
+
+ @subject_name = ["用户数", "项目数", "组织数", "Issue数", "Issue评论数", "PR数", "Commit数"]
+ @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]
+
+ 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
+
+
end
def month_active_user
@@ -42,6 +104,19 @@ class Admins::DashboardsController < Admins::BaseController
render_ok(data: data)
end
+ def baidu_tongji
+ tongji_service = Baidu::TongjiService.new
+ redirect_to tongji_service.code_url
+ end
+
+ def baidu_tongji_auth
+ if params[:code].present?
+ tongji_service = Baidu::TongjiService.new
+ tongji_service.get_access_token(params[:code])
+ end
+ redirect_to "/admins/"
+ end
+
def evaluate
names = []
data = []
@@ -63,8 +138,12 @@ class Admins::DashboardsController < Admins::BaseController
Time.now.beginning_of_day..Time.now.end_of_day
end
- def current_week
+ def pre_7_days
7.days.ago.end_of_day..Time.now.end_of_day
+ end
+
+ def current_week
+ Time.now.beginning_of_week..Time.now.end_of_day
end
def current_month
@@ -72,6 +151,7 @@ class Admins::DashboardsController < Admins::BaseController
end
def pre_week
- 14.days.ago.end_of_day..7.days.ago.end_of_day
+ # 14.days.ago.end_of_day..7.days.ago.end_of_day
+ Time.now.prev_week..Time.now.prev_week.end_of_week
end
end
\ No newline at end of file
diff --git a/app/controllers/admins/identity_verifications_controller.rb b/app/controllers/admins/identity_verifications_controller.rb
index 51d5e423c..1db1a9883 100644
--- a/app/controllers/admins/identity_verifications_controller.rb
+++ b/app/controllers/admins/identity_verifications_controller.rb
@@ -14,12 +14,14 @@ class Admins::IdentityVerificationsController < Admins::BaseController
end
def update
- if @identity_verification.update(update_params)
+ if update_params[:state] == "已拒绝" && update_params[:description].blank?
+ flash[:danger] = '拒绝理由不能为空'
+ render 'edit'
+ else
+ UserAction.create(action_id: @identity_verification.id, action_type: "UpdateIdentityVerifications", user_id: current_user.id, :ip => request.remote_ip, data_bank: @identity_verification.attributes.to_json)
+ @identity_verification.update(update_params)
redirect_to admins_identity_verifications_path
flash[:success] = "更新成功"
- else
- redirect_to admins_identity_verifications_path
- flash[:danger] = "更新失败"
end
end
diff --git a/app/controllers/admins/issues_rank_controller.rb b/app/controllers/admins/issues_rank_controller.rb
new file mode 100644
index 000000000..79450fbfb
--- /dev/null
+++ b/app/controllers/admins/issues_rank_controller.rb
@@ -0,0 +1,29 @@
+class Admins::IssuesRankController < Admins::BaseController
+
+ def index
+ @statistics = DailyProjectStatistic.where('date >= ? AND date <= ?', begin_date, end_date)
+ @statistics = @statistics.group(:project_id).joins(:project).select("project_id,
+ sum(issues) as issues,
+ sum(closed_issues) as closed_issues,
+ projects.issues_count as issues_count")
+ @statistics = @statistics.order("#{sort_by} #{sort_direction}").limit(50)
+ end
+
+ private
+ def begin_date
+ params.fetch(:begin_date, (Date.yesterday-7.days).to_s)
+ end
+
+ def end_date
+ params.fetch(:end_date, Date.yesterday.to_s)
+ end
+
+ def sort_by
+ DailyProjectStatistic.column_names.include?(params.fetch(:sort_by, "issues")) ? params.fetch(:sort_by, "issues") : "issues"
+ end
+
+ def sort_direction
+ %w(desc asc).include?(params.fetch(:sort_direction, "desc")) ? params.fetch(:sort_direction, "desc") : "desc"
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/admins/projects_controller.rb b/app/controllers/admins/projects_controller.rb
index f1f797043..dc3f6030a 100644
--- a/app/controllers/admins/projects_controller.rb
+++ b/app/controllers/admins/projects_controller.rb
@@ -5,7 +5,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 ?", "%#{search}%").order("#{sort_by} #{sort_direction}")
+ projects = Project.where("name like ? OR identifier LIKE ?", "%#{search}%", "%#{search}%").order("#{sort_by} #{sort_direction}")
@projects = paginate projects.includes(:owner, :members, :issues, :versions, :attachments, :project_score)
end
diff --git a/app/controllers/admins/projects_rank_controller.rb b/app/controllers/admins/projects_rank_controller.rb
index e4d8046e0..8db0961b7 100644
--- a/app/controllers/admins/projects_rank_controller.rb
+++ b/app/controllers/admins/projects_rank_controller.rb
@@ -17,11 +17,11 @@ class Admins::ProjectsRankController < Admins::BaseController
private
def begin_date
- params.fetch(:begin_date, (Date.today-7.days).to_s)
+ params.fetch(:begin_date, (Date.yesterday-7.days).to_s)
end
def end_date
- params.fetch(:end_date, Date.today.to_s)
+ params.fetch(:end_date, Date.yesterday.to_s)
end
def sort_by
diff --git a/app/controllers/admins/site_pages_controller.rb b/app/controllers/admins/site_pages_controller.rb
index f0e05e71d..306c91627 100644
--- a/app/controllers/admins/site_pages_controller.rb
+++ b/app/controllers/admins/site_pages_controller.rb
@@ -29,8 +29,12 @@ class Admins::SitePagesController < Admins::BaseController
end
def update
- @site_page.update(update_params)
- flash[:success] = '保存成功'
+ if update_params[:state] == "false" && update_params[:state_description].blank?
+ flash[:danger] = '关闭站点理由不能为空'
+ else
+ @site_page.update(update_params)
+ flash[:success] = '保存成功'
+ end
render 'edit'
end
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/issue_links_controller.rb b/app/controllers/api/pm/issue_links_controller.rb
new file mode 100644
index 000000000..844c01597
--- /dev/null
+++ b/app/controllers/api/pm/issue_links_controller.rb
@@ -0,0 +1,21 @@
+class Api::Pm::IssueLinksController < Api::Pm::BaseController
+ before_action :load_project
+ before_action :load_issue
+ def index
+ @links = @issue.pm_links.where(be_linkable_type: 'Issue')
+ end
+
+ def create
+ params[:link_ids].map { |e| @issue.pm_links.find_or_create_by(be_linkable_type: 'Issue', be_linkable_id: e) }
+ render_ok
+ end
+
+ def destroy
+ @link = @issue.pm_links.find_by(be_linkable_type: 'Issue', be_linkable_id: params[:id])
+ if @link.try(:destroy)
+ render_ok
+ else
+ 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..b74cf29f0
--- /dev/null
+++ b/app/controllers/api/pm/issues_controller.rb
@@ -0,0 +1,215 @@
+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::V1::Issues::CreateService.call(@project, issue_params, current_user)
+ render 'api/v1/issues/create'
+ end
+
+ def update
+ @object_result = Api::V1::Issues::UpdateService.call(@project, @issue, issue_params, current_user)
+ render 'api/v1/issues/update'
+ end
+
+ def batch_update
+ @object_result = Api::V1::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::V1::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::V1::Issues::DeleteService.call(@project, @issue, current_user)
+ if @object_result
+ render_ok
+ else
+ render_error('删除疑修失败!')
+ end
+ end
+
+ private
+ 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..14f386860
--- /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/v1/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..2c343cf3a
--- /dev/null
+++ b/app/controllers/api/pm/projects_controller.rb
@@ -0,0 +1,145 @@
+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])
+ 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|
+ [1,2,3].map{ |type|
+ 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,
+ "2": type_status_data[2][1].to_i + type_status_data[2][2].to_i,
+ "3": type_status_data[3][1].to_i + type_status_data[3][2].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
+ data = {
+ pie_chart: type_count_data,
+ bar_chart: type_status_data,
+ open_data: open_data
+ }
+ render_ok(data: data)
+ end
+
+ def polyline
+ return tip_exception '参数错误' if params[:pm_project_id].blank?
+ time_line = (Time.current.beginning_of_day - 6.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: {}
+ }
+ 7.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..b004d93d2
--- /dev/null
+++ b/app/controllers/api/pm/sprint_issues_controller.rb
@@ -0,0 +1,108 @@
+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 = 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 bcb0c4e86..d9db4b99c 100644
--- a/app/controllers/api/v1/base_controller.rb
+++ b/app/controllers/api/v1/base_controller.rb
@@ -55,9 +55,14 @@ class Api::V1::BaseController < ApplicationController
return render_forbidden if !current_user.admin? && !@project.operator?(current_user) && !(@project.fork_project.present? && @project.fork_project.operator?(current_user))
end
+ def require_member_above
+ @project = load_project
+ return render_forbidden if !current_user.admin? && !@project.member?(current_user)
+ end
+
# 具有对仓库的访问权限
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/gitlink_competition_applies_controller.rb b/app/controllers/api/v1/gitlink_competition_applies_controller.rb
new file mode 100644
index 000000000..5f2d7ef28
--- /dev/null
+++ b/app/controllers/api/v1/gitlink_competition_applies_controller.rb
@@ -0,0 +1,37 @@
+class Api::V1::GitlinkCompetitionAppliesController < Api::V1::BaseController
+
+ def create
+ return render_error("请输入正确的竞赛ID") unless params[:competition_id].present?
+ return render_error("请输入正确的队伍ID") unless params[:team_id].present?
+ return render_error("请输入正确的队伍成员信息") unless params[:team_members].is_a?(Array)
+ params[:team_members].each do |member|
+ apply = GitlinkCompetitionApply.find_or_create_by(competition_id: params[:competition_id], team_id: params[:team_id], educoder_login: member[:login])
+ apply.competition_identifier = params[:competition_identifier]
+ apply.team_name = params[:team_name]
+ apply.school_name = member[:school_name]
+ apply.nickname = member[:nickname]
+ apply.identity = member[:identity]
+ apply.role = member[:role]
+ apply.email = member[:email]
+ user_info = get_user_info_by_educoder_login(member[:login])
+ apply.phone = user_info["phone"]
+ apply.save
+ end
+ render_ok
+ end
+
+ def get_user_info_by_educoder_login(edu_login)
+ req_params = { "login" => "#{edu_login}", "private_token" => "hriEn3UwXfJs3PmyXnqQ" }
+ api_url= "https://data.educoder.net"
+ client = Faraday.new(url: api_url)
+ response = client.public_send("get", "/api/sources/get_user_info_by_login", req_params)
+ result = JSON.parse(response.body)
+
+ return nil if result["status"].to_s != "0"
+
+ # login 邮箱 手机号 姓名 学校/单位
+ user_info = result["data"]
+
+ return user_info
+ 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/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/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/project_datasets_controller.rb b/app/controllers/api/v1/project_datasets_controller.rb
new file mode 100644
index 000000000..995c1872e
--- /dev/null
+++ b/app/controllers/api/v1/project_datasets_controller.rb
@@ -0,0 +1,10 @@
+class Api::V1::ProjectDatasetsController < Api::V1::BaseController
+
+ def index
+ return render_error("请输入正确的项目id字符串") unless params[:ids].present?
+ ids = params[:ids].split(",")
+ @project_datasets = ProjectDataset.where(project_id: ids).includes(:license, :project)
+ @project_datasets = kaminari_unlimit_paginate(@project_datasets)
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/actions/actions_controller.rb b/app/controllers/api/v1/projects/actions/actions_controller.rb
new file mode 100644
index 000000000..3825b4685
--- /dev/null
+++ b/app/controllers/api/v1/projects/actions/actions_controller.rb
@@ -0,0 +1,31 @@
+class Api::V1::Projects::Actions::ActionsController < Api::V1::Projects::Actions::BaseController
+
+ def index
+ begin
+ gitea_result = $gitea_hat_client.get_repos_actions_by_owner_repo(@project&.owner&.login, @project&.identifier)
+ @data = gitea_result[:data]["Workflows"]
+ rescue
+ @data = []
+ end
+ end
+
+ def disable
+ return render_error("请输入正确的流水线文件!") if params[:workflow].blank?
+ gitea_result = $gitea_hat_client.post_repos_actions_disable(@project&.owner&.login, @project&.identifier, {query: {workflow: params[:workflow]}}) rescue nil
+ if gitea_result
+ render_ok
+ else
+ render_error("禁用流水线失败")
+ end
+ end
+
+ def enable
+ return render_error("请输入正确的流水线文件!") if params[:workflow].blank?
+ gitea_result = $gitea_hat_client.post_repos_actions_enable(@project&.owner&.login, @project&.identifier, {query: {workflow: params[:workflow]}}) rescue nil
+ if gitea_result
+ render_ok
+ else
+ render_error("取消禁用流水线失败")
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/actions/base_controller.rb b/app/controllers/api/v1/projects/actions/base_controller.rb
new file mode 100644
index 000000000..d76b446ad
--- /dev/null
+++ b/app/controllers/api/v1/projects/actions/base_controller.rb
@@ -0,0 +1,4 @@
+class Api::V1::Projects::Actions::BaseController < Api::V1::BaseController
+ before_action :require_public_and_member_above
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/actions/runs_controller.rb b/app/controllers/api/v1/projects/actions/runs_controller.rb
new file mode 100644
index 000000000..ff7c28e85
--- /dev/null
+++ b/app/controllers/api/v1/projects/actions/runs_controller.rb
@@ -0,0 +1,46 @@
+class Api::V1::Projects::Actions::RunsController < Api::V1::Projects::Actions::BaseController
+
+ 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
+ end
+
+ def rerun
+ return render_error("请输入正确的流水线记录ID!") if params[:run_id].blank?
+ gitea_result = $gitea_hat_client.post_repos_actions_runs_rerun_by_owner_repo_run(@project&.owner&.login, @project&.identifier, params[:run_id]) rescue nil
+ if gitea_result
+ render_ok
+ else
+ render_error("重启所有流水线任务失败")
+ end
+ end
+
+ def job_rerun
+ return render_error("请输入正确的流水线记录ID!") if params[:run_id].blank?
+ return render_error("请输入正确的流水线任务ID") if params[:job].blank?
+ gitea_result = $gitea_hat_client.post_repos_actions_runs_jobs_rerun_by_owner_repo_run_job(@project&.owner&.login, @project&.identifier, params[:run_id], params[:job]) rescue nil
+ if gitea_result
+ render_ok
+ else
+ render_error("重启流水线任务失败")
+ end
+ end
+
+ def job_show
+ @result_object = Api::V1::Projects::Actions::Runs::JobShowService.call(@project, params[:run_id], params[:job], params[:log_cursors], current_user&.gitea_token)
+ end
+
+ def job_logs
+ return render_error("请输入正确的流水线记录ID!") if params[:run_id].blank?
+ return render_error("请输入正确的流水线任务ID") if params[:job].blank?
+ domain = GiteaService.gitea_config[:domain]
+ api_url = GiteaService.gitea_config[:hat_base_url]
+
+ url = "/repos/#{@owner.login}/#{@repository.identifier}/actions/runs/#{CGI.escape(params[:run_id])}/jobs/#{CGI.escape(params[:job])}/logs"
+ file_path = [domain, api_url, url].join
+ file_path = [file_path, "access_token=#{@owner&.gitea_token}"].join("?")
+
+ redirect_to file_path
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/branches_controller.rb b/app/controllers/api/v1/projects/branches_controller.rb
index 40f44fea5..2bb7fdaf5 100644
--- a/app/controllers/api/v1/projects/branches_controller.rb
+++ b/app/controllers/api/v1/projects/branches_controller.rb
@@ -1,15 +1,38 @@
class Api::V1::Projects::BranchesController < Api::V1::BaseController
before_action :require_public_and_member_above, only: [:index, :all]
+ def gitee
+ url = URI("https://gitee.com/api/v5/repos/#{params[:owner]}/#{params[:repo]}/branches?access_token=#{params[:token]}&page=#{page}&per_page=#{limit}")
+ https = Net::HTTP.new(url.host, url.port)
+ https.use_ssl = true
+ request = Net::HTTP::Get.new(url)
+ response = https.request(request)
+ render :json => response.read_body
+ end
+
+ def github
+ url = URI("https://api.github.com/repos/#{params[:owner]}/#{params[:repo]}/branches?page=#{page}&per_page=#{limit}")
+ https = Net::HTTP.new(url.host, url.port)
+ https.use_ssl = true
+
+ request = Net::HTTP::Get.new(url)
+ request["Authorization"] = "Bearer #{params[:token]}"
+ request["Accept"] = "application/vnd.github+json"
+ request["X-GitHub-Api-Version"] = "2022-11-28"
+
+ response = https.request(request)
+ render :json => response.read_body
+ end
+
def index
- @result_object = Api::V1::Projects::Branches::ListService.call(@project, {name: params[:keyword], page: page, limit: limit}, current_user&.gitea_token)
+ @result_object = Api::V1::Projects::Branches::ListService.call(@project, {name: params[:keyword], state: params[:state], page: page, limit: limit}, current_user&.gitea_token)
end
def all
@result_object = Api::V1::Projects::Branches::AllListService.call(@project, current_user&.gitea_token)
end
- before_action :require_operate_above, only: [:create, :destroy]
+ before_action :require_operate_above, only: [:create, :destroy, :restore]
def create
@result_object = Api::V1::Projects::Branches::CreateService.call(@project, branch_params, current_user&.gitea_token)
@@ -33,6 +56,15 @@ class Api::V1::Projects::BranchesController < Api::V1::BaseController
end
end
+ def restore
+ @result_object = Api::V1::Projects::Branches::RestoreService.call(@project, params[:branch_id], params[:branch_name], current_user&.gitea_token)
+ if @result_object
+ return render_ok
+ else
+ return render_error('恢复分支失败!')
+ end
+ end
+
before_action :require_manager_above, only: [:update_default_branch]
def update_default_branch
diff --git a/app/controllers/api/v1/projects/commits_controller.rb b/app/controllers/api/v1/projects/commits_controller.rb
index a1545ae6f..98fdc290f 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]
+ before_action :require_public_and_member_above, only: [:index, :diff, :recent]
def index
@result_object = Api::V1::Projects::Commits::ListService.call(@project, {page: page, limit: limit, sha: params[:sha]}, current_user&.gitea_token)
@@ -9,4 +9,11 @@ class Api::V1::Projects::CommitsController < Api::V1::BaseController
def diff
@result_object = Api::V1::Projects::Commits::DiffService.call(@project, params[:sha], current_user&.gitea_token)
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]
+ @object_detail = hash[:detail]
+ puts @object_detail
+ end
end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/datasets_controller.rb b/app/controllers/api/v1/projects/datasets_controller.rb
new file mode 100644
index 000000000..8690ba529
--- /dev/null
+++ b/app/controllers/api/v1/projects/datasets_controller.rb
@@ -0,0 +1,51 @@
+class Api::V1::Projects::DatasetsController < Api::V1::BaseController
+ before_action :require_public_and_member_above, only: [:show]
+ before_action :require_member_above, only: [:create, :update]
+ before_action :find_dataset, only: [:update, :show]
+ before_action :check_menu_authorize
+
+ def create
+ ::Projects::Datasets::CreateForm.new(dataset_params).validate!
+ return render_error('该项目下已存在数据集!') if @project.project_dataset.present?
+ @project_dataset = ProjectDataset.new(dataset_params.merge!(project_id: @project.id))
+ if @project_dataset.save!
+ render_ok
+ else
+ render_error('创建数据集失败!')
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def update
+ ::Projects::Datasets::CreateForm.new(dataset_params).validate!
+ @project_dataset.attributes = dataset_params
+ if @project_dataset.save!
+ render_ok
+ else
+ render_error("更新数据集失败!")
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def show
+ @attachments = kaminari_paginate(@project_dataset.attachments.includes(:author))
+ end
+
+ private
+ def dataset_params
+ params.permit(:title, :description, :license_id, :paper_content)
+ end
+
+ def find_dataset
+ @project_dataset = @project.project_dataset
+ return render_not_found unless @project_dataset.present?
+ end
+
+ def check_menu_authorize
+ return render_not_found unless @project.has_menu_permission("dataset")
+ 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..c1bcc8299
--- /dev/null
+++ b/app/controllers/api/v1/projects/pipelines_controller.rb
@@ -0,0 +1,460 @@
+class Api::V1::Projects::PipelinesController < Api::V1::BaseController
+ before_action :require_operate_above
+
+ def index
+ @pipelines = Action::Pipeline.where(project_id: @project.id).order(updated_at: :desc)
+ @pipelines = paginate @pipelines
+ 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(params[:pipeline_name], params[:pipeline_json])
+ tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
+ @pipeline.yaml = pipeline_yaml
+ 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 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(pipeline_name, pipeline_json)
+ if pipeline_json.present? && pipeline_json.present?
+ @pipeline_name = pipeline_name
+ params_nodes = pipeline_json["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["name"]) }
+ on_nodes = pipeline_json["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["name"]) }
+ @on_nodes = build_nodes(on_nodes)
+ @steps_nodes = build_nodes(params_nodes)
+ 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/, "")
+ else
+ pipeline_yaml = params[:pipeline_yaml]
+ end
+ pipeline_yaml
+ end
+
+ def build_test_yaml
+ @pipeline_name = "I like it"
+ params_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["name"]) }
+ on_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["name"]) }
+ @on_nodes = build_nodes(on_nodes)
+ @steps_nodes = []
+ params_nodes.each do |input_node|
+ # Rails.logger.info "input_node=====0===#{input_node["name"]}======#{input_node["inputs"]}"
+ node = Action::Node.find_by(name: input_node["name"])
+ next if node.blank?
+ node.cust_name = input_node["label"] if input_node["label"].present?
+ run_values = {}
+ input_values = {}
+ if input_node["inputs"].present?
+ Rails.logger.info "@inputs=====11===#{input_node["name"]}======#{input_node["inputs"]}"
+ input_node["inputs"].each do |input|
+ # Rails.logger.info "@inputs.input_name===#{input[:name]}"
+ # Rails.logger.info "@inputs.input_value===#{input["value"]}"
+ 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
+ # Rails.logger.info "@input_values run_values===#{node.run_values.to_json}"
+ # Rails.logger.info "@input_values input_values===#{node.input_values.to_json}"
+ end
+ @steps_nodes.push(node)
+ end
+ Rails.logger.info "@@on_nodes===#{@on_nodes.to_json}"
+ Rails.logger.info "@steps_nodes===#{@steps_nodes.to_json}"
+ 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: Base64.encode64(@pipeline.yaml).gsub(/\n/, ''),
+ message: "#{opt} pipeline",
+ 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["name"])
+ next if node.blank?
+ node.cust_name = input_node["labelf"] if input_node["label"].present?
+ run_values = {}
+ input_values = {}
+ if input_node["inputs"].present?
+ Rails.logger.info "@inputs=====11===#{input_node["name"]}======#{input_node["inputs"]}"
+ input_node["inputs"].each do |input|
+ # Rails.logger.info "@inputs.input_name===#{input[:name]}"
+ # Rails.logger.info "@inputs.input_value===#{input["value"]}"
+ 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 demo
+ {
+ "nodes": [{
+ "id": "on-schedule-2fcf505",
+ "name": "on-schedule",
+ "full_name": "on-schedule",
+ "description": " 定时器计划器",
+ "icon": "https://testforgeplus.trustie.net/api/attachments/0445403c-5d9e-4495-8414-339f87981ca1",
+ "action_node_types_id": 3,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "inputs": [{
+ "id": 8,
+ "name": "cron",
+ "input_type": "input",
+ "description": "示例:\r\n- cron: '20 8 * * *'",
+ "is_required": true,
+ "value": "- corn: '0 10 * * *'"
+ }],
+ "x": 586,
+ "y": 165.328125,
+ "label": "on-schedule",
+ "img": "https://testforgeplus.trustie.net/api/attachments/0445403c-5d9e-4495-8414-339f87981ca1",
+ "isCluster": false,
+ "type": "rect-node",
+ "size": [110, 36],
+ "labelCfg": {
+ "style": {
+ "fill": "transparent",
+ "fontSize": 0,
+ "boxShadow": "0px 0px 12px rgba(75, 84, 137, 0.05)",
+ "overflow": "hidden",
+ "x": -20,
+ "y": 0,
+ "textAlign": "left",
+ "textBaseline": "middle"
+ }
+ },
+ "style": {
+ "active": {
+ "fill": "rgb(247, 250, 255)",
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 2,
+ "shadowColor": "rgb(95, 149, 255)",
+ "shadowBlur": 10
+ },
+ "selected": {
+ "fill": "rgb(255, 255, 255)",
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 4,
+ "shadowColor": "rgb(95, 149, 255)",
+ "shadowBlur": 10,
+ "text-shape": {
+ "fontWeight": 500
+ }
+ },
+ "highlight": {
+ "fill": "rgb(223, 234, 255)",
+ "stroke": "#4572d9",
+ "lineWidth": 2,
+ "text-shape": {
+ "fontWeight": 500
+ }
+ },
+ "inactive": {
+ "fill": "rgb(247, 250, 255)",
+ "stroke": "rgb(191, 213, 255)",
+ "lineWidth": 1
+ },
+ "disable": {
+ "fill": "rgb(250, 250, 250)",
+ "stroke": "rgb(224, 224, 224)",
+ "lineWidth": 1
+ },
+ "nodeSelected": {
+ "fill": "red",
+ "shadowColor": "red",
+ "stroke": "red",
+ "text-shape": {
+ "fill": "red",
+ "stroke": "red"
+ }
+ },
+ "fill": "#fff",
+ "stroke": "transparent",
+ "cursor": "pointer",
+ "radius": 10,
+ "overflow": "hidden",
+ "lineWidth": 0.5,
+ "shadowColor": "rgba(75,84,137,0.05)",
+ "shadowBlur": 12
+ },
+ "cron": "- corn: '0 10 * * *'",
+ "depth": 0
+ }, {
+ "id": "actions/setup-node@v3-257f29d",
+ "name": "node",
+ "full_name": "actions/setup-node@v3",
+ "description": "",
+ "icon": "https://testforgeplus.trustie.net/api/attachments/c4774fc1-ecd9-47fd-9878-1847bdaf98f6",
+ "action_node_types_id": 1,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "inputs": [{
+ "id": 2,
+ "name": "node-version",
+ "input_type": "select",
+ "is_required": false,
+ "value": 55
+ }],
+ "x": 608,
+ "y": 357.328125,
+ "label": "node",
+ "img": "https://testforgeplus.trustie.net/api/attachments/c4774fc1-ecd9-47fd-9878-1847bdaf98f6",
+ "isCluster": false,
+ "type": "rect-node",
+ "size": [110, 36],
+ "labelCfg": {
+ "style": {
+ "fill": "transparent",
+ "fontSize": 0,
+ "boxShadow": "0px 0px 12px rgba(75, 84, 137, 0.05)",
+ "overflow": "hidden",
+ "x": -20,
+ "y": 0,
+ "textAlign": "left",
+ "textBaseline": "middle"
+ }
+ },
+ "style": {
+ "active": {
+ "fill": "rgb(247, 250, 255)",
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 2,
+ "shadowColor": "rgb(95, 149, 255)",
+ "shadowBlur": 10
+ },
+ "selected": {
+ "fill": "rgb(255, 255, 255)",
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 4,
+ "shadowColor": "rgb(95, 149, 255)",
+ "shadowBlur": 10,
+ "text-shape": {
+ "fontWeight": 500
+ }
+ },
+ "highlight": {
+ "fill": "rgb(223, 234, 255)",
+ "stroke": "#4572d9",
+ "lineWidth": 2,
+ "text-shape": {
+ "fontWeight": 500
+ }
+ },
+ "inactive": {
+ "fill": "rgb(247, 250, 255)",
+ "stroke": "rgb(191, 213, 255)",
+ "lineWidth": 1
+ },
+ "disable": {
+ "fill": "rgb(250, 250, 250)",
+ "stroke": "rgb(224, 224, 224)",
+ "lineWidth": 1
+ },
+ "nodeSelected": {
+ "fill": "red",
+ "shadowColor": "red",
+ "stroke": "red",
+ "text-shape": {
+ "fill": "red",
+ "stroke": "red"
+ }
+ },
+ "fill": "#fff",
+ "stroke": "transparent",
+ "cursor": "pointer",
+ "radius": 10,
+ "overflow": "hidden",
+ "lineWidth": 0.5,
+ "shadowColor": "rgba(75,84,137,0.05)",
+ "shadowBlur": 12
+ },
+ "depth": 0,
+ "node-version": 55
+ }],
+ "edges": [{
+ "source": "on-schedule-2fcf505",
+ "target": "actions/setup-node@v3-257f29d",
+ "style": {
+ "active": {
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 1
+ },
+ "selected": {
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 2,
+ "shadowColor": "rgb(95, 149, 255)",
+ "shadowBlur": 10,
+ "text-shape": {
+ "fontWeight": 500
+ }
+ },
+ "highlight": {
+ "stroke": "rgb(95, 149, 255)",
+ "lineWidth": 2,
+ "text-shape": {
+ "fontWeight": 500
+ }
+ },
+ "inactive": {
+ "stroke": "rgb(234, 234, 234)",
+ "lineWidth": 1
+ },
+ "disable": {
+ "stroke": "rgb(245, 245, 245)",
+ "lineWidth": 1
+ },
+ "endArrow": {
+ "path": "M 6,0 L 9,-1.5 L 9,1.5 Z",
+ "d": 4.5,
+ "fill": "#CDD0DC"
+ },
+ "cursor": "pointer",
+ "lineWidth": 1,
+ "opacity": 1,
+ "stroke": "#CDD0DC",
+ "radius": 1
+ },
+ "nodeStateStyle": {
+ "hover": {
+ "opacity": 1,
+ "stroke": "#8fe8ff"
+ }
+ },
+ "labelCfg": {
+ "autoRotate": true,
+ "style": {
+ "fontSize": 10,
+ "fill": "#FFF"
+ }
+ },
+ "id": "edge-0.96904321945951241716516719464",
+ "startPoint": {
+ "x": 586,
+ "y": 183.578125,
+ "anchorIndex": 1
+ },
+ "endPoint": {
+ "x": 608,
+ "y": 339.078125,
+ "anchorIndex": 0
+ },
+ "sourceAnchor": 1,
+ "targetAnchor": 0,
+ "type": "cubic-vertical",
+ "curveOffset": [0, 0],
+ "curvePosition": [0.5, 0.5],
+ "minCurveOffset": [0, 0]
+ }],
+ "combos": []
+ }
+ end
+end
diff --git a/app/controllers/api/v1/projects/sync_repositories_controller.rb b/app/controllers/api/v1/projects/sync_repositories_controller.rb
new file mode 100644
index 000000000..f8b466420
--- /dev/null
+++ b/app/controllers/api/v1/projects/sync_repositories_controller.rb
@@ -0,0 +1,148 @@
+class Api::V1::Projects::SyncRepositoriesController < Api::V1::BaseController
+ before_action :require_public_and_member_above, except: [:sync]
+ before_action :load_project, only: [:sync]
+
+ def index
+ @sync_repositories = @project.sync_repositories
+ @group_sync_repository = @project.sync_repositories.group(:type, :external_repo_address, :sync_granularity, :external_token).count
+ end
+
+ def create
+ @sync_repository1, @sync_repository2, @sync_repository_branch1, @sync_repository_branch2 = Api::V1::Projects::SyncRepositories::CreateService.call(@project, sync_repository_params)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def update_info
+ return render_error("请输入正确的同步仓库ID") unless params[:sync_repository_ids].present?
+ Api::V1::Projects::SyncRepositories::UpdateService.call(@project, params[:sync_repository_ids], sync_repository_update_params)
+ render_ok
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def sync
+ return render_error("请输入正确的同步方向!") if params[:sync_direction].blank?
+ if params[:repo_type].present?
+ @sync_repositories = SyncRepository.where(project: @project, type: params[:repo_type], sync_direction: params[:sync_direction])
+ else
+ @sync_repositories = SyncRepository.where(project: @project, sync_direction: params[:sync_direction])
+ end
+ branch = params[:payload].present? ? JSON.parse(params[:payload])["ref"].split("/")[-1] : params[:ref].split("/")[-1] rescue nil
+ if params[:sync_direction].to_i == 1
+ @sync_repository_branches = SyncRepositoryBranch.where(sync_repository_id: @sync_repositories, gitlink_branch_name: branch, enable: true)
+ else
+ @sync_repository_branches = SyncRepositoryBranch.where(sync_repository_id: @sync_repositories, external_branch_name: branch, enable: true)
+ end
+ # 全部分支同步暂时不做
+ # @sync_repositories.each do |item|
+ # TouchSyncJob.perform_later(item)
+ # end
+ @sync_repository_branches.each do |item|
+ TouchSyncJob.set(wait: 5.seconds).perform_later(item)
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def unbind
+ return render_error("请输入正确的同步仓库ID") unless params[:sync_repository_ids].present?
+ @sync_repositories = SyncRepository.where(id: params[:sync_repository_ids].split(","))
+ @sync_repositories.each do |repo|
+ # Reposync::DeleteRepoService.call(repo.repo_name) # 解绑操作放在回调里
+ Api::V1::Projects::Webhooks::DeleteService.call(@project, repo.webhook_gid)
+ repo.destroy
+ end
+ render_ok
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def change_enable
+ return render_error("请输入正确的仓库类型") if params[:repo_type].blank?
+ return render_error("请输入正确的分支名称") if params[:gitlink_branch_name].blank? || params[:external_branch_name].blank?
+ # return render_error("请输入正确的状态") if params[:enable].blank?
+ @sync_repository_branches = SyncRepositoryBranch.joins(:sync_repository).where(sync_repositories: {project_id: @project.id, type: params[:repo_type]}, gitlink_branch_name: params[:gitlink_branch_name], external_branch_name: params[:external_branch_name])
+ if @sync_repository_branches.update_all({enable: params[:enable]})
+ @sync_repository_branches.each do |branch|
+ branch_sync_direction = branch&.sync_repository&.sync_direction.to_i
+ if branch_sync_direction == 1
+ Reposync::UpdateBranchStatusService.call(branch&.sync_repository&.repo_name, branch.gitlink_branch_name, params[:enable])
+ else
+ Reposync::UpdateBranchStatusService.call(branch&.sync_repository&.repo_name, branch.external_branch_name, params[:enable])
+ end
+ TouchSyncJob.perform_later(branch) if params[:enable] && branch_sync_direction == params[:first_sync_direction].to_i
+ end
+ render_ok
+ else
+ render_error("更新失败!")
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def create_branch
+ return render_error("请输入正确的同步仓库ID") unless params[:sync_repository_ids].present?
+ return render_error("请输入正确的Gitlink分支名称") unless params[:gitlink_branch_name].present?
+ return render_error("请输入正确的外部仓库分支名称") unless params[:external_branch_name].present?
+ return render_error("请输入正确的首次同步方向") unless params[:first_sync_direction].present?
+
+ params[:sync_repository_ids].split(",").each do |id|
+ repo = SyncRepository.find_by_id id
+ branch = Reposync::CreateSyncBranchService.call(repo.repo_name, params[:gitlink_branch_name], params[:external_branch_name])
+ return render_error(branch[2]) if branch[0].to_i !=0
+ sync_branch = SyncRepositoryBranch.create!(sync_repository_id: id, gitlink_branch_name: params[:gitlink_branch_name], external_branch_name: params[:external_branch_name], reposync_branch_id: branch[1]['id'])
+ TouchSyncJob.perform_later(sync_branch) if params[:first_sync_direction].to_i == repo.sync_direction
+ end
+ render_ok
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def branches
+ return render_error("请输入正确的同步仓库ID") unless params[:sync_repository_ids].present?
+ @sync_repository_branches = SyncRepositoryBranch.where(sync_repository_id: params[:sync_repository_ids].split(","))
+ @sync_repository_branches = @sync_repository_branches.ransack(gitlink_branch_name_or_external_branch_name_cont: params[:branch_name]).result if params[:branch_name].present?
+ @group_sync_repository_branch = @sync_repository_branches.joins(:sync_repository).group("sync_repositories.type, sync_repository_branches.gitlink_branch_name, sync_repository_branches.external_branch_name").select("sync_repositories.type as type,max(sync_repository_branches.updated_at) as updated_at, sync_repository_branches.gitlink_branch_name, sync_repository_branches.external_branch_name").sort_by{|i|i.updated_at}
+ @each_json = []
+ @group_sync_repository_branch.each do |item|
+ branches = @sync_repository_branches.joins(:sync_repository).where(sync_repositories: {type: item.type}, gitlink_branch_name: item.gitlink_branch_name, external_branch_name: item.external_branch_name).order(sync_time: :desc)
+ branch = branches.first
+ @each_json << {
+ gitlink_branch_name: item.gitlink_branch_name,
+ external_branch_name: item.external_branch_name,
+ type: branch&.sync_repository&.type,
+ sync_time: branch.sync_time.present? ? branch.sync_time.strftime("%Y-%m-%d %H:%M:%S") : nil,
+ sync_status: branch.sync_status,
+ enable: branch.enable,
+ enable_num: branch.enable ? 1 : 0,
+ created_at: branch.created_at.to_i,
+ reposync_branch_ids: branches.pluck(:reposync_branch_id)
+ }
+ end
+ @each_json = @each_json.sort_by{|h| [-h[:enable_num], h[:created_at]]}
+ render :json => {total_count: @group_sync_repository_branch.count, sync_repository_branches: @each_json}
+ end
+
+ def history
+ return render_error("请输入正确的同步分支ID") unless params[:reposync_branch_ids]
+ @branch = SyncRepositoryBranch.find_by(reposync_branch_id: params[:reposync_branch_ids].split(",")[0])
+ _, @reposync_branch_logs, @total_count, _ = Reposync::GetLogsService.call(nil, params[:reposync_branch_ids], page, limit)
+ end
+
+ private
+ def sync_repository_params
+ params.permit(:type, :external_token, :external_repo_address, :sync_granularity, :external_branch_name, :gitlink_branch_name, :first_sync_direction)
+ end
+
+ def sync_repository_update_params
+ params.permit(:external_token, :external_repo_address)
+ end
+
+end
\ No newline at end of file
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..bde3913e4
--- /dev/null
+++ b/app/controllers/api/v1/sonarqubes_controller.rb
@@ -0,0 +1,120 @@
+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
+ render_ok
+ end
+
+ def insert_file
+ sonar_scanner_content = {
+ filepath: '.gitea/workflows/SonarScanner.yaml',
+ branch: params[:branch],
+ new_branch: nil,
+ content: 'b246CiAgIyBUcmlnZ2VyIGFuYWx5c2lzIHdoZW4gcHVzaGluZyB0byB5b3VyIG1haW4gYnJhbmNoZXMsIGFuZCB3aGVuIGNyZWF0aW5nIGEgcHVsbCByZXF1ZXN0LgogIHB1c2g6CiAgICBicmFuY2hlczoKICAgICAgLSBtYWluCiAgICAgIC0gbWFzdGVyCiAgICAgIC0gZGV2ZWxvcAogICAgICAtICdyZWxlYXNlcy8qKicKICBwdWxsX3JlcXVlc3Q6CiAgICAgIHR5cGVzOiBbb3BlbmVkLCBzeW5jaHJvbml6ZSwgcmVvcGVuZWRdCgpuYW1lOiBNYWluIFdvcmtmbG93CmpvYnM6CiAgc29uYXJxdWJlOgogICAgcnVucy1vbjogdWJ1bnR1LWxhdGVzdAogICAgc3RlcHM6CiAgICAtIHVzZXM6IGFjdGlvbnMvY2hlY2tvdXRAdjQKICAgICAgd2l0aDoKICAgICAgICAjIERpc2FibGluZyBzaGFsbG93IGNsb25lcyBpcyByZWNvbW1lbmRlZCBmb3IgaW1wcm92aW5nIHRoZSByZWxldmFuY3kgb2YgcmVwb3J0aW5nCiAgICAgICAgZmV0Y2gtZGVwdGg6IDAKICAgIC0gbmFtZTogU29uYXJRdWJlIFNjYW4KICAgICAgdXNlczogc29uYXJzb3VyY2Uvc29uYXJxdWJlLXNjYW4tYWN0aW9uQG1hc3RlcgogICAgICBlbnY6CiAgICAgICAgU09OQVJfVE9LRU46ICR7eyBzZWNyZXRzLlNPTkFSX1RPS0VOIH19CiAgICAgICAgU09OQVJfSE9TVF9VUkw6ICAke3sgc2VjcmV0cy5TT05BUl9IT1NUX1VSTCB9fQ==',
+ 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?
+ sonar_scanner_content[:content] = Base64.decode64(sonar_scanner_content[:content])
+ Gitea::UpdateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_scanner_content.merge(sha:sonar_scanner_exit.result['sha']))
+ else
+ 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]}-#{params[:repo]}\nsonar.sources=.",
+ "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[:components],
+ s: params[:s],
+ impactSoftwareQualities: params[:impactSoftwareQualities],
+ issueStatuses: params[:issueStatuses],
+ ps: params[:ps],
+ p: params[:s],
+ facets: params[:facets],
+ additionalFields: params[:additionalFields],
+ timeZone: params[:timeZone]
+ }
+ data = Sonarqube.client.get('/api/issues/search', params_data)
+ render_ok data
+ end
+
+ def ce_component
+ params_data = {
+ components: params[:components]
+ }
+ data = Sonarqube.client.get('/api/ce/component', params_data)
+ render_ok data
+ end
+
+ def sources_issue_snippet
+ params_data = {
+ issueKey: params[:issueKey]
+ }
+ data = Sonarqube.client.get('/api/sources/issue_snippets', params_data)
+ render_ok data
+ end
+
+ def rules_show
+ params_data = {
+ key: params[:key]
+ }
+ data = Sonarqube.client.get('/api/rules/show', params_data)
+ render_ok data
+ end
+
+ def measures_search_history
+ params_data = {
+ from: params[:form],
+ component: params[:component],
+ metrics: params[:metrics],
+ ps: params[:ps]
+ }
+ data = Sonarqube.client.get('/api/measures/search_history', params_data)
+ render_ok data
+ end
+
+ def measures_component
+ params_data = {
+ component: params[:component],
+ additionalFields: params[:additionalFields],
+ metricKeys: params[:metricKeys],
+ }
+ data = Sonarqube.client.get('/api/measures/component', params_data)
+ render_ok data
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 8981a0995..bf2fb85c1 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -318,19 +318,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
@@ -363,15 +363,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)
end
+ autologin_user
end
def try_to_autologin
@@ -715,7 +714,7 @@ class ApplicationController < ActionController::Base
end
def find_user_with_id
- @user = User.find_by_id params[:user_id]
+ @user = User.find_by(type: 'User', id: params[:user_id])
# render_not_found("未找到’#{params[:login]}’相关的用户") unless @user
render_error("未找到相关的用户") unless @user
end
diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb
index ecc4760b5..c824b6ce3 100644
--- a/app/controllers/attachments_controller.rb
+++ b/app/controllers/attachments_controller.rb
@@ -95,6 +95,9 @@ class AttachmentsController < ApplicationController
@attachment.disk_directory = month_folder
@attachment.cloud_url = remote_path
@attachment.uuid = SecureRandom.uuid
+ @attachment.description = params[:description]
+ @attachment.container_id = params[:container_id]
+ @attachment.container_type = params[:container_type]
@attachment.save!
else
logger.info "文件已存在,id = #{@attachment.id}, filename = #{@attachment.filename}"
@@ -124,7 +127,7 @@ class AttachmentsController < ApplicationController
# 附件为视频时,点击播放
def preview_attachment
- attachment = Attachment.find_by(id: params[:id])
+ attachment = Attachment.where_id_or_uuid(params[:id]).first
dir_path = "#{Rails.root}/public/preview"
Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
if params[:status] == "preview"
diff --git a/app/controllers/bind_users_controller.rb b/app/controllers/bind_users_controller.rb
index f5ed33809..b8c25dd7d 100644
--- a/app/controllers/bind_users_controller.rb
+++ b/app/controllers/bind_users_controller.rb
@@ -8,7 +8,7 @@ class BindUsersController < ApplicationController
bind_user = User.try_to_login(params[:username], params[:password])
tip_exception '用户名或者密码错误' if bind_user.blank?
tip_exception '用户名或者密码错误' unless bind_user.check_password?(params[:password].to_s)
- tip_exception '参数错误' unless ["qq", "wechat", "gitee", "github", "educoder"].include?(params[:type].to_s)
+ tip_exception '参数错误' unless ["qq", "wechat", "gitee", "github", "educoder", "acge"].include?(params[:type].to_s)
tip_exception '该账号已被绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s)
"OpenUsers::#{params[:type].to_s.capitalize}".constantize.create!(user: bind_user, uid: session[:unionid])
diff --git a/app/controllers/oauth/acge_controller.rb b/app/controllers/oauth/acge_controller.rb
new file mode 100644
index 000000000..6f7c82039
--- /dev/null
+++ b/app/controllers/oauth/acge_controller.rb
@@ -0,0 +1,67 @@
+class Oauth::AcgeController < Oauth::BaseController
+ include RegisterHelper
+
+ def create
+ begin
+ uid = params['uid'].to_s.strip
+ tip_exception("uid不能为空") if uid.blank?
+ redirect_uri = params['redirect_uri'].to_s.strip
+ tip_exception("redirect_uri不能为空") if redirect_uri.blank?
+ email = params['email'].to_s.strip
+ tip_exception("email不能为空") if email.blank?
+ phone = params['phone'].to_s.strip
+ tip_exception("phone不能为空") if phone.blank?
+ name = params['name'].to_s.strip
+ tip_exception("name不能为空") if name.blank?
+
+ open_user = OpenUsers::Acge.find_by(uid: uid)
+ if open_user.present? && open_user.user.present?
+ successful_authentication(open_user.user)
+ redirect_to redirect_uri
+ return
+ else
+ if current_user.blank? || !current_user.logged?
+ session[:unionid] = uid
+ user = User.find_by(mail: email) || User.find_by(phone: phone)
+ if user.present?
+ OpenUsers::Acge.create!(user: user, uid: uid)
+ successful_authentication(user)
+ redirect_to redirect_uri
+
+ return
+ else
+ username = uid
+ password = SecureRandom.hex(4)
+ reg_result = autologin_register(username, email, password, 'acge', phone, name)
+ existing_rows = CSV.read("public/操作系统大赛用户信息.csv")
+ new_row = [username, email, password, phone, name]
+ existing_rows << new_row
+ CSV.open("public/操作系统大赛用户信息.csv", 'wb') do |csv|
+ existing_rows.each { |row| csv << row }
+ end
+ if reg_result[:message].blank?
+ open_user = OpenUsers::Acge.create!(user_id: reg_result[:user][:id], uid: uid)
+ successful_authentication(open_user.user)
+ redirect_to redirect_uri
+
+ return
+ else
+ render_error(reg_result[:message])
+ end
+ end
+ else
+ OpenUsers::Acge.create!(user: current_user, uid: uid)
+ successful_authentication(current_user)
+ redirect_to redirect_uri
+
+ return
+ end
+ end
+
+ Rails.logger.info("[OAuth2] session[:unionid] -> #{session[:unionid]}")
+ # redirect_to "/bindlogin/acge?redirect_uri=#{redirect_uri}"
+ rescue Exception => ex
+ render_error(ex.message)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/organizations/organization_users_controller.rb b/app/controllers/organizations/organization_users_controller.rb
index d9035dc14..171e9dcae 100644
--- a/app/controllers/organizations/organization_users_controller.rb
+++ b/app/controllers/organizations/organization_users_controller.rb
@@ -4,17 +4,31 @@ class Organizations::OrganizationUsersController < Organizations::BaseController
before_action :check_user_can_edit_org, only: [:destroy]
def index
- @organization_users = @organization.organization_users.includes(:user)
+ # @organization_users = @organization.organization_users.includes(:user)
+ # if params[:search].present?
+ # search = params[:search].to_s.downcase
+ # user_condition_users = User.like(search).to_sql
+ # team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql
+ # users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users")
+ #
+ # @organization_users = @organization_users.where(user_id: users).distinct
+ # end
+ #
+ # @organization_users = kaminari_paginate(@organization_users)
+
+ organization_user_ids = @organization.organization_users.pluck(:user_id).uniq
+ project_member_user_ids = @organization.projects.joins(:members).pluck("members.user_id").uniq
+ ids = organization_user_ids + project_member_user_ids
+ users = User.where(id: ids).reorder(Arel.sql("FIELD(users.id,#{ids.join(',')})"))
if params[:search].present?
search = params[:search].to_s.downcase
user_condition_users = User.like(search).to_sql
team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql
- users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users")
+ user_ids = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users").pluck(:id)
- @organization_users = @organization_users.where(user_id: users).distinct
+ users = users.where(id: user_ids)
end
-
- @organization_users = kaminari_paginate(@organization_users)
+ @users = kaminari_paginate(users)
end
def pm_check_user
diff --git a/app/controllers/organizations/projects_controller.rb b/app/controllers/organizations/projects_controller.rb
index ab5c9ef5d..1180a1a51 100644
--- a/app/controllers/organizations/projects_controller.rb
+++ b/app/controllers/organizations/projects_controller.rb
@@ -10,6 +10,8 @@ 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.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/organizations/teams_controller.rb b/app/controllers/organizations/teams_controller.rb
index 09f5bc3f0..56172a61e 100644
--- a/app/controllers/organizations/teams_controller.rb
+++ b/app/controllers/organizations/teams_controller.rb
@@ -67,7 +67,18 @@ class Organizations::TeamsController < Organizations::BaseController
tip_exception("组织团队不允许被删除") if @team.owner?
ActiveRecord::Base.transaction do
Gitea::Organization::Team::DeleteService.call(@organization.gitea_token, @team.gtid)
+ other_user_ids = @organization.team_users.where.not(team_id: @team.id).pluck(:user_id)
+ team_user_ids = @team.team_users.pluck(:user_id)
+ # 当前删除团队中成员在其他组织其他团队不存在的成员需清除组织
+ remove_user_ids = team_user_ids - other_user_ids
+ Rails.logger.info "remove_user_ids ===========> #{remove_user_ids}"
@team.destroy!
+ if remove_user_ids.present?
+ User.where(id: remove_user_ids).each do |user|
+ @organization.organization_users.find_by(user_id: user.id).destroy!
+ Gitea::Organization::OrganizationUser::DeleteService.call(@organization.gitea_token, @organization.login, user.login)
+ end
+ end
end
render_ok
rescue Exception => e
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 2045eb7fd..6ddc3410a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -21,6 +21,7 @@ class ProjectsController < ApplicationController
menu.append(menu_hash_by_name("issues")) if @project.has_menu_permission("issues")
menu.append(menu_hash_by_name("pulls")) if @project.has_menu_permission("pulls") && @project.forge?
menu.append(menu_hash_by_name("devops")) if @project.has_menu_permission("devops") && @project.forge?
+ menu.append(menu_hash_by_name("dataset")) if @project.has_menu_permission("dataset") && @project.forge?
menu.append(menu_hash_by_name("versions")) if @project.has_menu_permission("versions")
menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") && @project.forge?
menu.append(menu_hash_by_name("services")) if @project.has_menu_permission("services") && @project.forge? && (current_user.admin? || @project.member?(current_user.id))
@@ -42,7 +43,8 @@ class ProjectsController < ApplicationController
@total_count =
if category_id.blank? && params[:search].blank? && params[:topic_id].blank?
# 默认查询时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 = 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?
@projects.total_count
else
@@ -58,7 +60,10 @@ class ProjectsController < ApplicationController
OpenProjectDevOpsJob.set(wait: 5.seconds).perform_later(@project&.id, current_user.id)
UpdateProjectTopicJob.perform_later(@project.id) if @project.id.present?
end
- rescue Exception => e
+ rescue Gitea::Api::ServerError => ex
+ uid_logger_error(ex.message)
+ tip_exception(ex.http_code, ex.message)
+ rescue ApplicationService::Error => e
uid_logger_error(e.message)
tip_exception(e.message)
end
@@ -193,13 +198,19 @@ class ProjectsController < ApplicationController
default_branch: @project.default_branch
}
Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params)
+ elsif project_params.has_key?("has_actions")
+ gitea_params = {
+ has_actions: project_params[:has_actions]
+ }
+ Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params)
else
validate_params = project_params.slice(:name, :description,
:project_category_id, :project_language_id, :private, :identifier)
Projects::UpdateForm.new(validate_params.merge(user_id: @project.user_id, project_identifier: @project.identifier, project_name: @project.name)).validate!
- private = @project.forked_from_project.present? ? !@project.forked_from_project.is_public : params[:private] || false
+ private = params[:private].nil? ? !@project.is_public : params[:private]
+ private = @project.forked_from_project.present? ? !@project.forked_from_project.is_public : private
new_project_params = project_params.except(:private).merge(is_public: !private)
@project.update_attributes!(new_project_params)
@@ -232,15 +243,6 @@ 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
@@ -347,7 +349,7 @@ class ProjectsController < ApplicationController
def project_params
params.permit(:user_id, :name, :description, :repository_name, :website, :lesson_url, :default_branch, :identifier,
- :project_category_id, :project_language_id, :license_id, :ignore_id, :private,
+ :project_category_id, :project_language_id, :license_id, :ignore_id, :private, :has_actions,
:blockchain, :blockchain_token_all, :blockchain_init_token, :pr_view_admin)
end
diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb
index 037e201bf..1fe079f29 100644
--- a/app/controllers/pull_requests_controller.rb
+++ b/app/controllers/pull_requests_controller.rb
@@ -203,6 +203,7 @@ class PullRequestsController < ApplicationController
def pr_merge
return render_forbidden("你没有权限操作.") unless @project.operator?(current_user)
+ return normal_status(-1, "该分支存在冲突,无法自动合并.") unless @pull_request.conflict_files.blank?
if params[:do].blank?
normal_status(-1, "请选择合并方式")
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index e26559f74..a71c2ff1a 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -64,6 +64,7 @@ class RepositoriesController < ApplicationController
@entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name)
else
@entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call
+ return render_not_found if @entries.is_a?(Array) && @entries.blank?
@entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : []
@path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/"
end
diff --git a/app/controllers/version_releases_controller.rb b/app/controllers/version_releases_controller.rb
index ed608873e..2419a1fef 100644
--- a/app/controllers/version_releases_controller.rb
+++ b/app/controllers/version_releases_controller.rb
@@ -1,4 +1,5 @@
class VersionReleasesController < ApplicationController
+ include ApplicationHelper
before_action :load_repository
before_action :set_user
before_action :require_login, except: [:index, :show]
@@ -126,6 +127,16 @@ class VersionReleasesController < ApplicationController
end
end
+ def download
+ tip_exception(404, '您访问的页面不存在或已被删除') if params["tag_name"].blank? || params["filename"].blank?
+ version = @repository.version_releases.find_by(tag_name: params["tag_name"])
+ attachment = version.attachments.find_by(filename: params["filename"])
+ tip_exception(404, '您访问的页面不存在或已被删除') if attachment.blank?
+ send_file(absolute_path(local_path(attachment)), filename: attachment.title, stream: false, type: attachment.content_type.presence || 'application/octet-stream')
+ update_downloads(attachment)
+ # redirect_to "/api/attachments/#{attachment.uuid}"
+ end
+
private
def set_user
diff --git a/app/forms/organizations/create_form.rb b/app/forms/organizations/create_form.rb
index 2163f477e..1f40350b3 100644
--- a/app/forms/organizations/create_form.rb
+++ b/app/forms/organizations/create_form.rb
@@ -1,12 +1,12 @@
class Organizations::CreateForm < BaseForm
- NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
+ NAME_REGEX = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*$/ #只含有数字、字母、下划线不能以下划线开头和结尾
attr_accessor :name, :description, :website, :location, :repo_admin_change_team_access, :visibility, :max_repo_creation, :nickname, :original_name
validates :name, :nickname, :visibility, presence: true
validates :name, :nickname, length: { maximum: 100 }
validates :location, length: { maximum: 50 }
validates :description, length: { maximum: 200 }
- validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
+ validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能以数字或字母开头,仅支持横杠、下划线、点三种符号,不允许符号连续排列,长度4-50个字符" }
validate do
check_name(name) unless name.blank? || name == original_name
diff --git a/app/forms/projects/create_form.rb b/app/forms/projects/create_form.rb
index c133175c2..8ef7befac 100644
--- a/app/forms/projects/create_form.rb
+++ b/app/forms/projects/create_form.rb
@@ -28,6 +28,10 @@ class Projects::CreateForm < BaseForm
raise "ignore_id值无效." if ignore_id && Ignore.find_by(id: ignore_id).blank?
end
+ def check_auto_init
+ raise "auto_init值无效." if ignore_id && license_id && !auto_init
+ end
+
def check_owner
@project_owner = Owner.find_by(id: user_id)
raise "user_id值无效." if user_id && @project_owner.blank?
diff --git a/app/forms/projects/datasets/create_form.rb b/app/forms/projects/datasets/create_form.rb
new file mode 100644
index 000000000..c812ee17e
--- /dev/null
+++ b/app/forms/projects/datasets/create_form.rb
@@ -0,0 +1,15 @@
+class Projects::Datasets::CreateForm < BaseForm
+ attr_accessor :title, :description, :license_id, :paper_content
+
+
+ validates :title, presence: true, length: { maximum: 100 }
+ validates :description, presence: true, length: { maximum: 500 }
+ validates :paper_content, length: { maximum: 500 }
+
+ validate :check_license
+
+ def check_license
+ raise "license_id值无效. " if license_id && License.find_by(id: license_id).blank?
+ end
+
+end
\ No newline at end of file
diff --git a/app/helpers/action/node_helper.rb b/app/helpers/action/node_helper.rb
new file mode 100644
index 000000000..05d08da2f
--- /dev/null
+++ b/app/helpers/action/node_helper.rb
@@ -0,0 +1,2 @@
+module Action::NodeHelper
+end
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/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/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 b86226454..93e2bde76 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -67,6 +67,7 @@ module ProjectsHelper
jianmu_devops_url: jianmu_devops_url,
cloud_ide_saas_url: cloud_ide_saas_url(user),
open_blockchain: Site.has_blockchain? && project.use_blockchain,
+ has_dataset: project.project_dataset.present?,
ignore_id: project.ignore_id
}).compact
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index f364c11ff..360f7d03f 100644
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -132,6 +132,8 @@ module RepositoriesHelper
src_regex_3 = /src= (.*?) /
src_regex_4 = /src =(.*?) /
src_regex_5 = /src =(.*?) /
+ href_regex = /href=\"(.*?)\"/
+ href_regex_1 = /href=\'(.*?)\'/
ss_c = content.to_s.scan(s_regex_c)
ss = content.to_s.scan(s_regex)
ss_1 = content.to_s.scan(s_regex_1)
@@ -142,7 +144,9 @@ module RepositoriesHelper
ss_src_3 = content.to_s.scan(src_regex_3)
ss_src_4 = content.to_s.scan(src_regex_4)
ss_src_5 = content.to_s.scan(src_regex_5)
- total_sources = {ss_c: ss_c,ss: ss, ss_1: ss_1, ss_2: ss_2, ss_src: ss_src, ss_src_1: ss_src_1, ss_src_2: ss_src_2, ss_src_3: ss_src_3, ss_src_4: ss_src_4, ss_src_5: ss_src_5}
+ ss_href = content.to_s.scan(href_regex)
+ ss_href_1 = content.to_s.scan(href_regex_1)
+ total_sources = {ss_c: ss_c,ss: ss, ss_1: ss_1, ss_2: ss_2, ss_src: ss_src, ss_src_1: ss_src_1, ss_src_2: ss_src_2, ss_src_3: ss_src_3, ss_src_4: ss_src_4, ss_src_5: ss_src_5, ss_href: ss_href, ss_href_1: ss_href_1}
# total_sources.uniq!
total_sources.except(:ss, :ss_c).each do |k, sources|
sources.each do |s|
@@ -153,6 +157,7 @@ module RepositoriesHelper
ext = File.extname(s_content)[1..-1]
ext = ext.split("?")[0] if ext.include?("?")
if (image_type?(ext) || download_type(ext)) && !ext.blank?
+ s_content = s_content.starts_with?("/") ? s_content[1..-1] : s_content[0..-1]
s_content = File.expand_path(s_content, file_path)
s_content = s_content.split("#{Rails.root}/")[1]
# content = content.gsub(s[0], "/#{s_content}")
@@ -173,13 +178,17 @@ module RepositoriesHelper
content = content.gsub("src=#{s[0]}", "src=\'#{s_content}\'")
when 'ss_2'
content = content.gsub(/]:#{s[0]}/, "]: #{s_content.to_s.gsub(" ","").gsub("\r", "")}")
- else
+ when 'ss_href'
+ content = content.gsub("href=\"#{s[0]}\"", "href=\"#{s_content}\"")
+ when 'ss_href_1'
+ content = content.gsub("href=\'#{s[0]}\'", "href=\'#{s_content}\'")
+ else
content = content.gsub("(#{s[0]})", "(#{s_content})")
end
else
path = [owner&.login, repo&.identifier, 'tree', ref, file_path].join("/")
s_content = File.expand_path(s_content, path)
- s_content = s_content.split("#{Rails.root}/")[1]
+ s_content = s_content.split("#{Rails.root}")[1]
case k.to_s
when 'ss_src'
content = content.gsub("src=\"#{s[0]}\"", "src=\"/#{s_content}\"")
@@ -187,7 +196,11 @@ module RepositoriesHelper
content = content.gsub("src=\'#{s[0]}\'", "src=\'/#{s_content}\'")
when 'ss_2'
content = content.gsub(/]:#{s[0]}/, "]: /#{s_content.to_s.gsub(" ","").gsub("\r", "")}")
- else
+ when 'ss_href'
+ content = content.gsub("href=\"#{s[0]}\"", "href=\"#{s_content}\"")
+ when 'ss_href_1'
+ content = content.gsub("href=\'#{s[0]}\'", "href=\'#{s_content}\'")
+ else
content = content.gsub("(#{s[0]})", "(/#{s_content})")
end
end
diff --git a/app/interactors/gitea/delete_file_interactor.rb b/app/interactors/gitea/delete_file_interactor.rb
index 03ddf4230..f94b8a205 100644
--- a/app/interactors/gitea/delete_file_interactor.rb
+++ b/app/interactors/gitea/delete_file_interactor.rb
@@ -45,6 +45,7 @@ module Gitea
else
Rails.logger.error("Gitea::Repository::Entries::DeleteService error[#{response.status}]======#{response.body}")
@error = "删除失败,请确认该分支是否是保护分支。"
+ @error = "删除失败,参数sha不匹配。" if response.body.to_s.include?("sha does not match")
end
end
diff --git a/app/jobs/daily_platform_statistics_job.rb b/app/jobs/daily_platform_statistics_job.rb
new file mode 100644
index 000000000..5610f304b
--- /dev/null
+++ b/app/jobs/daily_platform_statistics_job.rb
@@ -0,0 +1,44 @@
+# 按天获取百度统计数据,pv,访问,ip和来源分类占比
+# 其他统计:前一周用户留存率
+class DailyPlatformStatisticsJob < ApplicationJob
+ queue_as :default
+
+ def perform(*args)
+ Rails.logger.info("*********开始统计*********")
+
+ tongji_service = Baidu::TongjiService.new
+ access_token = tongji_service.access_token
+ Rails.logger.info "job baidu_tongji_auth access_token ===== #{access_token}"
+ ActiveJob::Base.logger.info "job baidu_tongji_auth access_token ===== #{access_token}"
+ # 从最后一个记录日期开始,如果遗漏日期数据可以补充数据
+ last_date = DailyPlatformStatistic.order(:date).last
+ start_date = last_date.date
+ end_date = Time.now
+ if access_token.present?
+ tongji_service.overview_batch_add(start_date, end_date)
+
+ # 本周访问来源占比,每天记录一次,如果遗漏日期数据可以补充数据
+ tongji_service.source_from_batch_add(start_date, end_date)
+ end
+ # 周用户留存率
+ pre_week_user_ids = User.where(created_on: pre_week).pluck(:id).uniq
+ weekly_keep_user_count = User.where(id: pre_week_user_ids).where(last_login_on: current_week).count
+ weekly_keep_rate = format("%.2f", pre_week_user_ids.size > 0 ? weekly_keep_user_count.to_f / pre_week_user_ids.size : 0)
+
+ job_date = 1.days.ago
+ daily_statistic = DailyPlatformStatistic.find_or_initialize_by(date: job_date)
+ daily_statistic.weekly_keep_rate = weekly_keep_rate
+ daily_statistic.save
+ end
+
+ private
+
+ def current_week
+ Time.now.beginning_of_week..Time.now.end_of_day
+ end
+
+ def pre_week
+ # 7.days.ago.beginning_of_week..7.days.ago.beginning_of_week.end_of_week
+ Time.now.prev_week..Time.now.prev_week.end_of_week
+ end
+end
diff --git a/app/jobs/daily_project_statistics_job.rb b/app/jobs/daily_project_statistics_job.rb
index ad606f137..3672d1924 100644
--- a/app/jobs/daily_project_statistics_job.rb
+++ b/app/jobs/daily_project_statistics_job.rb
@@ -13,6 +13,7 @@ class DailyProjectStatisticsJob < ApplicationJob
praises = result["praises"].to_i
forks = result["forks"].to_i
issues = result["issues"].to_i
+ closed_issues = result["closed_issues"].to_i
pullrequests = result["pullrequests"].to_i
commits = result["commits"].to_i
score = visits *1 + watchers *5 + praises * 5 + forks * 10 + issues *5 + pullrequests * 10 + commits * 5
@@ -25,6 +26,7 @@ class DailyProjectStatisticsJob < ApplicationJob
praises: praises,
forks: forks,
issues: issues,
+ closed_issues: closed_issues,
pullrequests: pullrequests,
commits: commits
)
diff --git a/app/jobs/delay_expired_issue_and_milestone_job.rb b/app/jobs/delay_expired_issue_and_milestone_job.rb
new file mode 100644
index 000000000..4bba7aaf1
--- /dev/null
+++ b/app/jobs/delay_expired_issue_and_milestone_job.rb
@@ -0,0 +1,16 @@
+class DelayExpiredIssueAndMilestoneJob < ApplicationJob
+ queue_as :message
+
+ def perform
+ Issue.where(due_date: Date.today + 1.days).find_each do |issue|
+ SendTemplateMessageJob.perform_later('IssueExpire', issue.id) if Site.has_notice_menu?
+ end
+ Version.where(effective_date: Date.today + 1.days).find_each do |version|
+ SendTemplateMessageJob.perform_later('ProjectMilestoneEarlyExpired', version.id) if Site.has_notice_menu?
+ end
+ Version.where(effective_date: Date.today - 1.days).find_each do |version|
+ SendTemplateMessageJob.perform_later('ProjectMilestoneExpired', version.id) if Site.has_notice_menu?
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/jobs/delay_expired_issue_job.rb b/app/jobs/delay_expired_issue_job.rb
deleted file mode 100644
index ed390e15b..000000000
--- a/app/jobs/delay_expired_issue_job.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class DelayExpiredIssueJob < ApplicationJob
- queue_as :message
-
- def perform
- Issue.where(due_date: Date.today + 1.days).find_each do |issue|
- SendTemplateMessageJob.perform_later('IssueExpire', issue.id) if Site.has_notice_menu?
- end
- end
-
-end
\ No newline at end of file
diff --git a/app/jobs/send_template_message_job.rb b/app/jobs/send_template_message_job.rb
index 1df1d8c7d..1b309fe35 100644
--- a/app/jobs/send_template_message_job.rb
+++ b/app/jobs/send_template_message_job.rb
@@ -221,6 +221,20 @@ class SendTemplateMessageJob < ApplicationJob
receivers_email_string, email_title, email_content = MessageTemplate::ProjectMilestone.get_email_message_content(receiver, operator, milestone)
Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content)
end
+ when 'ProjectMilestoneExpired'
+ milestone_id = args[0]
+ milestone = Version.find_by_id(milestone_id)
+ return unless milestone.present? && milestone&.project.present?
+ receivers = User.where(id: milestone.user_id)
+ receivers_string, content, notification_url = MessageTemplate::ProjectMilestoneExpired.get_message_content(receivers, milestone)
+ Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {milestone_id: milestone_id, operator_id: operator_id})
+ when 'ProjectMilestoneEarlyExpired'
+ milestone_id = args[0]
+ milestone = Version.find_by_id(milestone_id)
+ return unless milestone.present? && milestone&.project.present?
+ receivers = User.where(id: milestone.user_id)
+ receivers_string, content, notification_url = MessageTemplate::ProjectMilestoneEarlyExpired.get_message_content(receivers, milestone)
+ Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {milestone_id: milestone_id, operator_id: operator_id})
when 'ProjectPraised'
operator_id, project_id = args[0], args[1]
operator = User.find_by_id(operator_id)
diff --git a/app/jobs/touch_sync_job.rb b/app/jobs/touch_sync_job.rb
new file mode 100644
index 000000000..3beaf52e5
--- /dev/null
+++ b/app/jobs/touch_sync_job.rb
@@ -0,0 +1,24 @@
+class TouchSyncJob < ApplicationJob
+ queue_as :default
+
+ def perform(touchable)
+ puts "aaaa"
+ case touchable.class.to_s
+ when 'SyncRepositories::Github' || 'SyncRepositories::Gitee'
+ Reposync::SyncRepoService.call(touchable.repo_name)
+ when 'SyncRepositoryBranch'
+ sync_repository = touchable.sync_repository
+ result = []
+ if sync_repository.sync_direction == 1
+ result = Reposync::SyncBranchService.call(sync_repository.repo_name, touchable.gitlink_branch_name, sync_repository.sync_direction)
+ else
+ result = Reposync::SyncBranchService.call(sync_repository.repo_name, touchable.external_branch_name, sync_repository.sync_direction)
+ end
+ if result.is_a?(Array) && result[0].to_i == 0
+ touchable.update_attributes!({sync_status: 1, sync_time: Time.now})
+ else
+ touchable.update_attributes!({sync_status: 2, sync_time: Time.now})
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/libs/custom_regexp.rb b/app/libs/custom_regexp.rb
index b735a631b..0a59d8748 100644
--- a/app/libs/custom_regexp.rb
+++ b/app/libs/custom_regexp.rb
@@ -9,7 +9,7 @@ module CustomRegexp
URL = /\Ahttps?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]\z/
IP = /^((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/
- URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i
+ URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i
# REPOSITORY_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9\-\_\.]+[a-zA-Z0-9]$/ #只含有数字、字母、下划线不能以下划线开头和结尾
REPOSITORY_NAME_REGEX = /^[a-zA-Z0-9\-\_\.]+[a-zA-Z0-9]$/ #只含有数字、字母、下划线不能以下划线开头和结尾
MD_REGEX = /^.+(\.[m|M][d|D])$/
diff --git a/app/models/action/node.rb b/app/models/action/node.rb
new file mode 100644
index 000000000..07d3be134
--- /dev/null
+++ b/app/models/action/node.rb
@@ -0,0 +1,64 @@
+# == Schema Information
+#
+# Table name: action_nodes
+#
+# id :integer not null, primary key
+# name :string(255)
+# full_name :string(255)
+# description :string(255)
+# icon :string(255)
+# action_node_types_id :integer
+# is_local :boolean default("0")
+# local_url :string(255)
+# yaml :text(65535)
+# sort_no :integer default("0")
+# use_count :integer default("0")
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# by_name (name)
+# index_action_nodes_on_action_types_id (action_node_types_id)
+# index_action_nodes_on_user_id (user_id)
+#
+
+class Action::Node < ApplicationRecord
+ self.table_name = 'action_nodes'
+ default_scope { order(sort_no: :asc) }
+
+ has_many :action_node_inputs, :class_name => 'Action::NodeInput', foreign_key: "action_nodes_id"
+ has_many :action_node_selects, :class_name => 'Action::NodeSelect', foreign_key: "action_nodes_id"
+ belongs_to :action_node_type, :class_name => 'Action::NodeType', foreign_key: "action_node_types_id"
+
+ belongs_to :user, optional: true
+
+ attr_accessor :cust_name, :run_values, :input_values
+
+
+ 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
+
+ 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
new file mode 100644
index 000000000..4f3825170
--- /dev/null
+++ b/app/models/action/node_input.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+#
+# Table name: action_node_inputs
+#
+# id :integer not null, primary key
+# action_nodes_id :integer
+# name :string(255)
+# input_type :string(255)
+# description :string(255)
+# is_required :boolean default("0")
+# sort_no :string(255) default("0")
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_action_node_inputs_on_action_nodes_id (action_nodes_id)
+# index_action_node_inputs_on_user_id (user_id)
+#
+
+class Action::NodeInput < ApplicationRecord
+ self.table_name = 'action_node_inputs'
+ default_scope { order(sort_no: :asc) }
+
+ belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
+end
diff --git a/app/models/action/node_select.rb b/app/models/action/node_select.rb
new file mode 100644
index 000000000..25be51f99
--- /dev/null
+++ b/app/models/action/node_select.rb
@@ -0,0 +1,39 @@
+# == Schema Information
+#
+# Table name: action_node_selects
+#
+# id :integer not null, primary key
+# action_nodes_id :integer
+# name :string(255)
+# val :string(255)
+# val_ext :string(255)
+# description :string(255)
+# download_url :string(255)
+# sort_no :integer default("0")
+# use_count :integer default("0")
+# user_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_action_node_selects_on_action_nodes_id (action_nodes_id)
+# index_action_node_selects_on_name (name)
+# index_action_node_selects_on_user_id (user_id)
+#
+
+class Action::NodeSelect < ApplicationRecord
+ self.table_name = 'action_node_selects'
+ default_scope { order(sort_no: :asc) }
+
+ belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
+ belongs_to :user, optional: true
+
+ def value
+ if self.val_ext.blank?
+ self.val
+ else
+ "#{self.val}@#{self.val_ext}"
+ end
+ end
+end
diff --git a/app/models/action/node_type.rb b/app/models/action/node_type.rb
new file mode 100644
index 000000000..7ce78b0fb
--- /dev/null
+++ b/app/models/action/node_type.rb
@@ -0,0 +1,18 @@
+# == Schema Information
+#
+# Table name: action_node_types
+#
+# id :integer not null, primary key
+# name :string(255)
+# description :string(255)
+# sort_no :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class Action::NodeType < ApplicationRecord
+ self.table_name = 'action_node_types'
+ default_scope { order(sort_no: :asc) }
+
+ has_many :action_nodes, :class_name => 'Action::Node', foreign_key: "action_node_types_id"
+end
diff --git a/app/models/action/pipeline.rb b/app/models/action/pipeline.rb
new file mode 100644
index 000000000..1dfbd0f24
--- /dev/null
+++ b/app/models/action/pipeline.rb
@@ -0,0 +1,37 @@
+# == 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
+
+
+end
diff --git a/app/models/action/template.rb b/app/models/action/template.rb
new file mode 100644
index 000000000..34b669f66
--- /dev/null
+++ b/app/models/action/template.rb
@@ -0,0 +1,20 @@
+# == Schema Information
+#
+# Table name: action_templates
+#
+# id :integer not null, primary key
+# name :string(255)
+# description :string(255)
+# img :string(255)
+# sort_no :string(255) default("0")
+# json :text(65535)
+# yaml :text(65535)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class Action::Template < ApplicationRecord
+ self.table_name = 'action_templates'
+ default_scope { order(sort_no: :asc) }
+
+end
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
index 9fd858bf3..be715921b 100644
--- a/app/models/attachment.rb
+++ b/app/models/attachment.rb
@@ -1,45 +1,46 @@
-# == Schema Information
-#
-# Table name: attachments
-#
-# id :integer not null, primary key
-# container_id :integer
-# container_type :string(30)
-# filename :string(255) default(""), not null
-# disk_filename :string(255) default(""), not null
-# filesize :integer default("0"), not null
-# content_type :string(255) default("")
-# digest :string(60) default(""), not null
-# downloads :integer default("0"), not null
-# author_id :integer default("0"), not null
-# created_on :datetime
-# description :text(65535)
-# disk_directory :string(255)
-# attachtype :integer default("1")
-# is_public :integer default("1")
-# copy_from :integer
-# quotes :integer default("0")
-# is_publish :integer default("1")
-# publish_time :datetime
-# resource_bank_id :integer
-# unified_setting :boolean default("1")
-# 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
-#
-# index_attachments_on_author_id (author_id)
-# index_attachments_on_container_id_and_container_type (container_id,container_type)
-# index_attachments_on_course_second_category_id (course_second_category_id)
-# index_attachments_on_created_on (created_on)
-# index_attachments_on_is_public (is_public)
-# index_attachments_on_quotes (quotes)
-#
-
+# == Schema Information
+#
+# Table name: attachments
+#
+# id :integer not null, primary key
+# container_id :integer
+# container_type :string(30)
+# filename :string(255) default(""), not null
+# disk_filename :string(255) default(""), not null
+# filesize :integer default("0"), not null
+# content_type :string(255) default("")
+# digest :string(60) default(""), not null
+# downloads :integer default("0"), not null
+# author_id :integer default("0"), not null
+# created_on :datetime
+# description :text(65535)
+# disk_directory :string(255)
+# attachtype :integer default("1")
+# is_public :integer default("1")
+# copy_from :integer
+# quotes :integer default("0")
+# is_publish :integer default("1")
+# publish_time :datetime
+# resource_bank_id :integer
+# unified_setting :boolean default("1")
+# 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
+#
+# index_attachments_on_author_id (author_id)
+# index_attachments_on_container_id_and_container_type (container_id,container_type)
+# index_attachments_on_course_second_category_id (course_second_category_id)
+# index_attachments_on_created_on (created_on)
+# index_attachments_on_is_public (is_public)
+# index_attachments_on_quotes (quotes)
+# index_attachments_on_uuid (uuid)
+#
+
@@ -72,7 +73,7 @@ class Attachment < ApplicationRecord
scope :unified_setting, -> {where("unified_setting = ? ", 1)}
scope :where_id_or_uuid, -> (id) { (Float(id) rescue nil).present? ? where(id: id) : where(uuid: id) }
- validates_length_of :description, maximum: 100, message: "不能超过100个字符"
+ validates_length_of :description, maximum: 255, message: "不能超过255个字符"
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)
diff --git a/app/models/concerns/project_operable.rb b/app/models/concerns/project_operable.rb
index 9a5efd129..34386aae2 100644
--- a/app/models/concerns/project_operable.rb
+++ b/app/models/concerns/project_operable.rb
@@ -21,68 +21,69 @@ module ProjectOperable
end
def add_member!(user_id, role_name='Developer')
- if self.owner.is_a?(Organization)
- case role_name
- when 'Manager'
- # 构建相应的团队
- team = self.owner.teams.admin.take
- if team.nil?
- team = Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false)
- gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
- team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
- end
-
- # 设置项目在团队中的访问权限
- team_project = TeamProject.build(self.user_id, team.id, self.id)
- tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
-
- # 新增对应的团队成员
- team_user = TeamUser.build(self.user_id, user_id, team.id)
- $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
-
- # 确保组织成员中有该用户
- OrganizationUser.build(self.user_id, user_id)
- when 'Developer'
- # 构建相应的团队
- team = self.owner.teams.write.take
- if team.nil?
- team = Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false)
- gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
- team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
- end
-
- # 设置项目在团队中的访问权限
- team_project = TeamProject.build(self.user_id, team.id, self.id)
- tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
-
- # 新增对应的团队成员
- team_user = TeamUser.build(self.user_id, user_id, team.id)
- $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
-
- # 确保组织成员中有该用户
- OrganizationUser.build(self.user_id, user_id)
- when 'Reporter'
- # 构建相应的团队
- team = self.owner.teams.read.take
- if team.nil?
- team = Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false)
- gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
- team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
- end
-
- # 设置项目在团队中的访问权限
- team_project = TeamProject.build(self.user_id, team.id, self.id)
- tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
-
- # 新增对应的团队成员
- team_user = TeamUser.build(self.user_id, user_id, team.id)
- $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
-
- # 确保组织成员中有该用户
- OrganizationUser.build(self.user_id, user_id)
- end
- end
- member = members.create!(user_id: user_id, team_user_id: team_user&.id)
+ # if self.owner.is_a?(Organization)
+ # case role_name
+ # when 'Manager'
+ # # 构建相应的团队
+ # team = self.owner.teams.admin.take
+ # if team.nil?
+ # team = Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false)
+ # gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
+ # team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
+ # end
+ #
+ # # 设置项目在团队中的访问权限
+ # team_project = TeamProject.build(self.user_id, team.id, self.id)
+ # tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
+ #
+ # # 新增对应的团队成员
+ # team_user = TeamUser.build(self.user_id, user_id, team.id)
+ # $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
+ #
+ # # 确保组织成员中有该用户
+ # OrganizationUser.build(self.user_id, user_id)
+ # when 'Developer'
+ # # 构建相应的团队
+ # team = self.owner.teams.write.take
+ # if team.nil?
+ # team = Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false)
+ # gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
+ # team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
+ # end
+ #
+ # # 设置项目在团队中的访问权限
+ # team_project = TeamProject.build(self.user_id, team.id, self.id)
+ # tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
+ #
+ # # 新增对应的团队成员
+ # team_user = TeamUser.build(self.user_id, user_id, team.id)
+ # $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
+ #
+ # # 确保组织成员中有该用户
+ # OrganizationUser.build(self.user_id, user_id)
+ # when 'Reporter'
+ # # 构建相应的团队
+ # team = self.owner.teams.read.take
+ # if team.nil?
+ # team = Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false)
+ # gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
+ # team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
+ # end
+ #
+ # # 设置项目在团队中的访问权限
+ # team_project = TeamProject.build(self.user_id, team.id, self.id)
+ # tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
+ #
+ # # 新增对应的团队成员
+ # team_user = TeamUser.build(self.user_id, user_id, team.id)
+ # $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
+ #
+ # # 确保组织成员中有该用户
+ # OrganizationUser.build(self.user_id, user_id)
+ # end
+ # end
+ # member = members.create!(user_id: user_id, team_user_id: team_user&.id)
+ member = members.create!(user_id: user_id)
set_developer_role(member, role_name)
end
@@ -116,71 +117,71 @@ module ProjectOperable
def change_member_role!(user_id, role)
member = self.member(user_id)
# 所有者为组织,并且该用户属于组织成员
- if self.owner.is_a?(Organization) && member.team_user.present?
- case role&.name
- when 'Manager'
- # 构建相应的团队
- team = self.owner.teams.admin.take
- if team.nil?
- team = Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false)
- gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
- team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
- end
-
- # 设置项目在团队中的访问权限
- team_project = TeamProject.build(self.user_id, team.id, self.id)
- tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
-
- # 更改对应的团队成员
- team_user = member.team_user
- $gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
- $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
- team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
-
- # 确保组织成员中有该用户
- OrganizationUser.build(self.user_id, user_id)
- when 'Developer'
- # 构建相应的团队
- team = self.owner.teams.write.take
- if team.nil?
- team = Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false)
- gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
- team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
- end
- # 设置项目在团队中的访问权限
- team_project = TeamProject.build(self.user_id, team.id, self.id)
- $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
-
- # 更改对应的团队成员
- team_user = member.team_user
- $gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
- $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
- team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
-
- OrganizationUser.build(self.user_id, user_id)
- when 'Reporter'
- # 构建相应的团队
- team = self.owner.teams.read.take
- if team.nil?
- team = Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false)
- gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
- team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
- end
-
- # 设置项目在团队中的访问权限
- team_project = TeamProject.build(self.user_id, team.id, self.id)
- tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
-
- # 更改对应的团队成员
- team_user = member.team_user
- $gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
- $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
- team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
-
- # 确保组织成员中有该用户
- OrganizationUser.build(self.user_id, user_id)
- end
- end
+ # if self.owner.is_a?(Organization) && member.team_user.present?
+ # case role&.name
+ # when 'Manager'
+ # # 构建相应的团队
+ # team = self.owner.teams.admin.take
+ # if team.nil?
+ # team = Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false)
+ # gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
+ # team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
+ # end
+ #
+ # # 设置项目在团队中的访问权限
+ # team_project = TeamProject.build(self.user_id, team.id, self.id)
+ # tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
+ #
+ # # 更改对应的团队成员
+ # team_user = member.team_user
+ # $gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
+ # $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
+ # team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
+ #
+ # # 确保组织成员中有该用户
+ # OrganizationUser.build(self.user_id, user_id)
+ # when 'Developer'
+ # # 构建相应的团队
+ # team = self.owner.teams.write.take
+ # if team.nil?
+ # team = Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false)
+ # gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
+ # team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
+ # end
+ # # 设置项目在团队中的访问权限
+ # team_project = TeamProject.build(self.user_id, team.id, self.id)
+ # $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
+ #
+ # # 更改对应的团队成员
+ # team_user = member.team_user
+ # $gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
+ # $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
+ # team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
+ #
+ # OrganizationUser.build(self.user_id, user_id)
+ # when 'Reporter'
+ # # 构建相应的团队
+ # team = self.owner.teams.read.take
+ # if team.nil?
+ # team = Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false)
+ # gteam = $gitea_client.post_orgs_teams_by_org(self.owner.login, {body: team.to_gitea_hash.to_json}) rescue nil
+ # team.update_attributes!({gtid: gteam["id"]}) unless gteam.nil?
+ # end
+ #
+ # # 设置项目在团队中的访问权限
+ # team_project = TeamProject.build(self.user_id, team.id, self.id)
+ # tp_result = $gitea_client.put_teams_repos_by_id_org_repo(team.gtid, self.owner.login, self.identifier) rescue nil
+ #
+ # # 更改对应的团队成员
+ # team_user = member.team_user
+ # $gitea_client.delete_teams_members_by_id_username(team_user.team.gtid, team_user.user&.login) rescue nil # 移除旧的
+ # $gitea_client.put_teams_members_by_id_username(team&.gtid, team_user.user&.login) rescue nil # 新增新的
+ # team_user.update_attributes!({team_id: team.id}) unless team.team_users.exists?(user_id: member.user_id)
+ #
+ # # 确保组织成员中有该用户
+ # OrganizationUser.build(self.user_id, user_id)
+ # end
+ # end
member.member_roles.last.update_attributes!(role: role)
end
diff --git a/app/models/daily_platform_statistic.rb b/app/models/daily_platform_statistic.rb
new file mode 100644
index 000000000..a904d9b1e
--- /dev/null
+++ b/app/models/daily_platform_statistic.rb
@@ -0,0 +1,24 @@
+# == Schema Information
+#
+# Table name: daily_platform_statistics
+#
+# id :integer not null, primary key
+# date :date
+# pv :integer default("0")
+# visitor :integer default("0")
+# ip :integer default("0")
+# weekly_keep_rate :float(24) default("0")
+# source_through :float(24) default("0")
+# source_link :float(24) default("0")
+# source_search :float(24) default("0")
+# source_custom :float(24) default("0")
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_daily_platform_statistics_on_date (date) UNIQUE
+#
+
+class DailyPlatformStatistic < ApplicationRecord
+end
diff --git a/app/models/daily_project_statistic.rb b/app/models/daily_project_statistic.rb
index f7fc0aad7..ffe51bdb3 100644
--- a/app/models/daily_project_statistic.rb
+++ b/app/models/daily_project_statistic.rb
@@ -2,18 +2,20 @@
#
# Table name: daily_project_statistics
#
-# id :integer not null, primary key
-# project_id :integer
-# date :string(255)
-# visits :integer default("0")
-# watchers :integer default("0")
-# praises :integer default("0")
-# forks :integer default("0")
-# issues :integer default("0")
-# pullrequests :integer default("0")
-# commits :integer default("0")
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :integer not null, primary key
+# project_id :integer
+# date :date
+# score :integer default("0")
+# visits :integer default("0")
+# watchers :integer default("0")
+# praises :integer default("0")
+# forks :integer default("0")
+# issues :integer default("0")
+# pullrequests :integer default("0")
+# commits :integer default("0")
+# created_at :datetime not null
+# updated_at :datetime not null
+# closed_issues :integer default("0")
#
# Indexes
#
diff --git a/app/models/gitlink_competition_apply.rb b/app/models/gitlink_competition_apply.rb
new file mode 100644
index 000000000..f3b7d4ce1
--- /dev/null
+++ b/app/models/gitlink_competition_apply.rb
@@ -0,0 +1,21 @@
+ # == 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)
+# login :string(255)
+# nickname :string(255)
+# phone :string(255)
+# identity :string(255)
+# role :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
+class GitlinkCompetitionApply < ApplicationRecord
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index f96bac8fb..cb8b98cce 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,48 +83,98 @@ 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 :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 :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 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'
+ end
+
+ def incre_or_decre_closed_issues_count
+ if previous_changes[:status_id].present? && is_issuely_issue?
+ if previous_changes[:status_id][1] == 5
+ CacheAsyncSetJob.perform_later("project_common_service", {closed_issues: 1}, self.project_id)
+ end
+ if previous_changes[:status_id][0] == 5
+ CacheAsyncSetJob.perform_later("project_common_service", {closed_issues: -1}, self.project_id)
+ end
+ end
+ end
def incre_project_common
- CacheAsyncSetJob.perform_later("project_common_service", {issues: 1}, self.project_id)
+ 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)
+ 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)
+ 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)
+ 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})
+ 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})
+ CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: -1}) if is_issuely_issue?
end
def get_assign_user
@@ -132,20 +183,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
@@ -153,11 +204,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
@@ -177,20 +228,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
@@ -220,7 +280,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
@@ -252,8 +312,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?
@@ -275,4 +335,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/message_template.rb b/app/models/message_template.rb
index 7ab7fdad8..75d64fa95 100644
--- a/app/models/message_template.rb
+++ b/app/models/message_template.rb
@@ -52,6 +52,8 @@ class MessageTemplate < ApplicationRecord
self.create(type: 'MessageTemplate::ProjectMilestone', sys_notice: '{nickname1}在 {nickname2}/{repository} 创建了一个里程碑:{name} ', notification_url: '{baseurl}/{owner}/{identifier}/milestones/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 新建了一个里程碑")
email_html = File.read("#{email_template_html_dir}/project_milestone_completed.html")
self.create(type: 'MessageTemplate::ProjectMilestoneCompleted', sys_notice: '在 {nickname}/{repository} 仓库,里程碑 {name} 的完成度已达到100%', notification_url: '{baseurl}/{owner}/{identifier}/milestones/{id}', email: email_html, email_title: "#{PLATFORM}: 仓库 {nickname}/{repository} 有里程碑已完成")
+ self.create(type: 'MessageTemplate::ProjectMilestoneEarlyExpired', sys_notice: '您创建的里程碑 {name} 已临近截止日期,请尽快处理.', notification_url: '{baseurl}/{owner}/{identifier}/milestones/{id}')
+ self.create(type: 'MessageTemplate::ProjectMilestoneExpired', sys_notice: '您创建的里程碑 {name} 已逾期,请及时更新进度或联系项目团队.', notification_url: '{baseurl}/{owner}/{identifier}/milestones/{id}')
self.create(type: 'MessageTemplate::ProjectPraised', sys_notice: '{nickname1} 点赞了你管理的仓库 {nickname2}/{repository} ', notification_url: '{baseurl}/{login}')
self.create(type: 'MessageTemplate::ProjectOpenDevOps', sys_notice: '您的仓库 {repository} 已成功开通引擎服务,可通过简单的节点编排完成自动化集成与部署。欢迎体验!', notification_url: '{baseurl}/{owner}/{identifier}/devops')
email_html = File.read("#{email_template_html_dir}/project_pull_request.html")
diff --git a/app/models/message_template/project_milestone_early_expired.rb b/app/models/message_template/project_milestone_early_expired.rb
new file mode 100644
index 000000000..3db1e1908
--- /dev/null
+++ b/app/models/message_template/project_milestone_early_expired.rb
@@ -0,0 +1,70 @@
+# == Schema Information
+#
+# Table name: message_templates
+#
+# id :integer not null, primary key
+# type :string(255)
+# sys_notice :text(65535)
+# email :text(65535)
+# created_at :datetime not null
+# updated_at :datetime not null
+# notification_url :string(255)
+# email_title :string(255)
+#
+
+# 我管理的仓库有里程碑完成
+class MessageTemplate::ProjectMilestoneEarlyExpired < MessageTemplate
+
+ # MessageTemplate::ProjectMilestoneEarlyExpired.get_message_content(User.where(login: 'yystopf'), Version.find(7))
+ def self.get_message_content(receivers, milestone)
+ receivers.each do |receiver|
+ if receiver.user_template_message_setting.present?
+ send_setting = receiver.user_template_message_setting.notification_body["ManageProject::MilestoneExpired"]
+ send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["ManageProject::MilestoneExpired"] : send_setting
+ receivers = receivers.where.not(id: receiver.id) unless send_setting
+ end
+ end
+ return '', '', '' if receivers.blank?
+ project = milestone&.project
+ owner = project&.owner
+ content = sys_notice.gsub('{nickname}', owner&.real_name).gsub('{repository}', project&.name).gsub('{name}', milestone&.name)
+ url = notification_url.gsub('{owner}', owner&.login).gsub('{identifier}', project&.identifier).gsub('{id}', milestone&.id.to_s)
+
+ return receivers_string(receivers), content, url
+ rescue => e
+ Rails.logger.info("MessageTemplate::MilestoneEarlyExpired.get_message_content [ERROR] #{e}")
+ return '', '', ''
+ end
+
+ def self.get_email_message_content(receiver, milestone)
+ if receiver.user_template_message_setting.present?
+ send_setting = receiver.user_template_message_setting.email_body["ManageProject::MilestoneExpired"]
+ send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_email_body["ManageProject::MilestoneExpired"] : send_setting
+ return '', '', '' unless send_setting
+ project = milestone&.project
+ owner = project&.owner
+ title = email_title
+ title.gsub!('{nickname}', owner&.real_name)
+ title.gsub!('{repository}', project&.name)
+
+ content = email
+ content.gsub!('{receiver}', receiver&.real_name)
+ content.gsub!('{baseurl}', base_url)
+ content.gsub!('{nickname}', owner&.real_name)
+ content.gsub!('{repository}', project&.name)
+ content.gsub!('{login}', owner&.login)
+ content.gsub!('{identifier}', project&.identifier)
+ content.gsub!('{id}', milestone&.id.to_s)
+ content.gsub!('{name}', milestone&.name)
+ content.gsub!('{platform}', PLATFORM)
+
+ return receiver&.mail, title, content
+ else
+ return '', '', ''
+ end
+
+ rescue => e
+ Rails.logger.info("MessageTemplate::MilestoneEarlyExpired.get_email_message_content [ERROR] #{e}")
+ return '', '', ''
+ end
+end
diff --git a/app/models/message_template/project_milestone_expired.rb b/app/models/message_template/project_milestone_expired.rb
new file mode 100644
index 000000000..de6b8c10c
--- /dev/null
+++ b/app/models/message_template/project_milestone_expired.rb
@@ -0,0 +1,70 @@
+# == Schema Information
+#
+# Table name: message_templates
+#
+# id :integer not null, primary key
+# type :string(255)
+# sys_notice :text(65535)
+# email :text(65535)
+# created_at :datetime not null
+# updated_at :datetime not null
+# notification_url :string(255)
+# email_title :string(255)
+#
+
+# 我管理的仓库有里程碑完成
+class MessageTemplate::ProjectMilestoneExpired < MessageTemplate
+
+ # MessageTemplate::ProjectMilestoneExpired.get_message_content(User.where(login: 'yystopf'), Version.find(7))
+ def self.get_message_content(receivers, milestone)
+ receivers.each do |receiver|
+ if receiver.user_template_message_setting.present?
+ send_setting = receiver.user_template_message_setting.notification_body["ManageProject::MilestoneExpired"]
+ send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_notification_body["ManageProject::MilestoneExpired"] : send_setting
+ receivers = receivers.where.not(id: receiver.id) unless send_setting
+ end
+ end
+ return '', '', '' if receivers.blank?
+ project = milestone&.project
+ owner = project&.owner
+ content = sys_notice.gsub('{nickname}', owner&.real_name).gsub('{repository}', project&.name).gsub('{name}', milestone&.name)
+ url = notification_url.gsub('{owner}', owner&.login).gsub('{identifier}', project&.identifier).gsub('{id}', milestone&.id.to_s)
+
+ return receivers_string(receivers), content, url
+ rescue => e
+ Rails.logger.info("MessageTemplate::ProjectMilestoneExpired.get_message_content [ERROR] #{e}")
+ return '', '', ''
+ end
+
+ def self.get_email_message_content(receiver, milestone)
+ if receiver.user_template_message_setting.present?
+ send_setting = receiver.user_template_message_setting.email_body["ManageProject::MilestoneExpired"]
+ send_setting = send_setting.nil? ? UserTemplateMessageSetting.init_email_body["ManageProject::MilestoneExpired"] : send_setting
+ return '', '', '' unless send_setting
+ project = milestone&.project
+ owner = project&.owner
+ title = email_title
+ title.gsub!('{nickname}', owner&.real_name)
+ title.gsub!('{repository}', project&.name)
+
+ content = email
+ content.gsub!('{receiver}', receiver&.real_name)
+ content.gsub!('{baseurl}', base_url)
+ content.gsub!('{nickname}', owner&.real_name)
+ content.gsub!('{repository}', project&.name)
+ content.gsub!('{login}', owner&.login)
+ content.gsub!('{identifier}', project&.identifier)
+ content.gsub!('{id}', milestone&.id.to_s)
+ content.gsub!('{name}', milestone&.name)
+ content.gsub!('{platform}', PLATFORM)
+
+ return receiver&.mail, title, content
+ else
+ return '', '', ''
+ end
+
+ rescue => e
+ Rails.logger.info("MessageTemplate::ProjectMilestoneExpired.get_email_message_content [ERROR] #{e}")
+ return '', '', ''
+ end
+end
diff --git a/app/models/open_users/acge.rb b/app/models/open_users/acge.rb
new file mode 100644
index 000000000..59963b91f
--- /dev/null
+++ b/app/models/open_users/acge.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::Acge < OpenUser
+ def nickname
+ extra&.[]('nickname')
+ end
+
+ def en_type
+ 'acge'
+ end
+end
diff --git a/app/models/organization.rb b/app/models/organization.rb
index cb829626e..f978611b1 100644
--- a/app/models/organization.rb
+++ b/app/models/organization.rb
@@ -64,7 +64,7 @@
class Organization < Owner
alias_attribute :name, :login
- NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
+ NAME_REGEX = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*$/ #只含有数字、字母、下划线不能以下划线开头和结尾
default_scope { where(type: "Organization") }
@@ -79,7 +79,7 @@ class Organization < Owner
validates :login, presence: true
validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, case_sensitive: false
- validates :login, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
+ validates :login, format: { with: NAME_REGEX, multiline: true, message: "只能以数字或字母开头,仅支持横杠、下划线、点三种符号,不允许符号连续排列,长度4-50个字符" }
delegate :description, :website, :location, :repo_admin_change_team_access, :recommend,
:visibility, :max_repo_creation, :num_projects, :num_users, :num_teams,
@@ -182,14 +182,6 @@ class Organization < Owner
organization_users.count
end
- def teams_count
- teams.count
- end
-
- def organization_users_count
- organization_users.count
- end
-
def real_name
name = lastname + firstname
name = name.blank? ? (nickname.blank? ? login : nickname) : name
@@ -217,4 +209,11 @@ class Organization < Owner
enabling_cla == true
end
+ def num_users
+ organization_user_ids = self.organization_users.pluck(:user_id).uniq
+ project_member_user_ids = self.projects.joins(:members).pluck("members.user_id").uniq
+ ids = organization_user_ids + project_member_user_ids
+ ids.uniq.size
+ end
+
end
diff --git a/app/models/page.rb b/app/models/page.rb
index 1c606760e..4b55c99b6 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -28,7 +28,7 @@ class Page < ApplicationRecord
belongs_to :project
# language_frame 前端语言框架
- enum language_frame: { hugo: 0, jekyll: 1, hexo: 2}
+ enum language_frame: { hugo: 0, jekyll: 1, hexo: 2, files: 3}
after_create do
PageService.genernate_user(user_id)
diff --git a/app/models/page_theme.rb b/app/models/page_theme.rb
index bce3d5f70..e5830d06c 100644
--- a/app/models/page_theme.rb
+++ b/app/models/page_theme.rb
@@ -13,7 +13,7 @@
#
class PageTheme < ApplicationRecord
- enum language_frame: { hugo: 0, jeklly: 1, hexo: 2}
+ enum language_frame: { hugo: 0, jeklly: 1, hexo: 2, files:3}
validates :name, presence: {message: "主题名不能为空"}, uniqueness: {message: "主题名已存在",scope: :language_frame},length: {maximum: 255}
def image
diff --git a/app/models/pm_link.rb b/app/models/pm_link.rb
new file mode 100644
index 000000000..91962bf7b
--- /dev/null
+++ b/app/models/pm_link.rb
@@ -0,0 +1,25 @@
+# == 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
+
+ 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 34e981508..245537408 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -55,14 +55,13 @@
# default_branch :string(255) default("master")
# website :string(255)
# lesson_url :string(255)
+# use_blockchain :boolean default("0")
# is_pinned :boolean default("0")
# recommend_index :integer default("0")
-# use_blockchain :boolean default("0")
# pr_view_admin :boolean default("0")
#
# Indexes
#
-# index_projects_on_forked_count (forked_count)
# index_projects_on_forked_from_project_id (forked_from_project_id)
# index_projects_on_identifier (identifier)
# index_projects_on_invite_code (invite_code)
@@ -72,7 +71,6 @@
# index_projects_on_license_id (license_id)
# index_projects_on_name (name)
# index_projects_on_platform (platform)
-# index_projects_on_praises_count (praises_count)
# index_projects_on_project_category_id (project_category_id)
# index_projects_on_project_language_id (project_language_id)
# index_projects_on_project_type (project_type)
@@ -80,7 +78,6 @@
# index_projects_on_rgt (rgt)
# index_projects_on_status (status)
# index_projects_on_updated_on (updated_on)
-# index_projects_on_user_id (user_id)
#
class Project < ApplicationRecord
@@ -90,6 +87,8 @@ class Project < ApplicationRecord
include ProjectOperable
include Dcodes
+ default_scope {where.not(id: 0)}
+
# common:开源托管项目
# mirror:普通镜像项目,没有定时同步功能
# sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作
@@ -137,6 +136,8 @@ class Project < ApplicationRecord
has_many :project_topics, through: :project_topic_ralates
has_many :commit_logs, dependent: :destroy
has_many :daily_project_statistics, dependent: :destroy
+ has_one :project_dataset, dependent: :destroy
+ has_many :sync_repositories, dependent: :destroy
after_create :incre_user_statistic, :incre_platform_statistic
after_save :check_project_members
before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data
@@ -486,6 +487,11 @@ class Project < ApplicationRecord
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
diff --git a/app/models/project_dataset.rb b/app/models/project_dataset.rb
new file mode 100644
index 000000000..ae4bb5789
--- /dev/null
+++ b/app/models/project_dataset.rb
@@ -0,0 +1,26 @@
+# == Schema Information
+#
+# Table name: project_datasets
+#
+# id :integer not null, primary key
+# title :string(255)
+# description :text(65535)
+# project_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# license_id :integer
+# paper_content :text(65535)
+#
+# Indexes
+#
+# index_project_datasets_on_license_id (license_id)
+# index_project_datasets_on_project_id (project_id)
+#
+
+class ProjectDataset < ApplicationRecord
+
+ belongs_to :project
+ belongs_to :license, optional: true
+ has_many :attachments, as: :container, dependent: :destroy
+
+end
diff --git a/app/models/project_unit.rb b/app/models/project_unit.rb
index 93e3668eb..d2b6d2085 100644
--- a/app/models/project_unit.rb
+++ b/app/models/project_unit.rb
@@ -17,12 +17,13 @@ class ProjectUnit < ApplicationRecord
belongs_to :project
- enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7, services: 8}
+ enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7, services: 8, dataset: 9}
validates :unit_type, uniqueness: { scope: :project_id}
def self.init_types(project_id, project_type='common')
unit_types = project_type == 'sync_mirror' ? ProjectUnit::unit_types.except("pulls") : ProjectUnit::unit_types
+ unit_types = unit_types.except("dataset")
unit_types.each do |_, v|
self.create!(project_id: project_id, unit_type: v)
end
diff --git a/app/models/sync_repositories/gitee.rb b/app/models/sync_repositories/gitee.rb
new file mode 100644
index 000000000..0a51b21c8
--- /dev/null
+++ b/app/models/sync_repositories/gitee.rb
@@ -0,0 +1,22 @@
+# == Schema Information
+#
+# Table name: sync_repositories
+#
+# id :integer not null, primary key
+# project_id :integer
+# type :string(255)
+# repo_name :string(255)
+# external_repo_address :string(255)
+# sync_granularity :integer
+# sync_direction :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_sync_repositories_on_project_id (project_id)
+#
+
+class SyncRepositories::Gitee < SyncRepository
+
+end
diff --git a/app/models/sync_repositories/github.rb b/app/models/sync_repositories/github.rb
new file mode 100644
index 000000000..1ef413a54
--- /dev/null
+++ b/app/models/sync_repositories/github.rb
@@ -0,0 +1,21 @@
+# == Schema Information
+#
+# Table name: sync_repositories
+#
+# id :integer not null, primary key
+# project_id :integer
+# type :string(255)
+# repo_name :string(255)
+# external_repo_address :string(255)
+# sync_granularity :integer
+# sync_direction :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_sync_repositories_on_project_id (project_id)
+#
+
+class SyncRepositories::Github < SyncRepository
+end
diff --git a/app/models/sync_repository.rb b/app/models/sync_repository.rb
new file mode 100644
index 000000000..94f7c6630
--- /dev/null
+++ b/app/models/sync_repository.rb
@@ -0,0 +1,35 @@
+# == Schema Information
+#
+# Table name: sync_repositories
+#
+# id :integer not null, primary key
+# project_id :integer
+# type :string(255)
+# repo_name :string(255)
+# external_repo_address :string(255)
+# sync_granularity :integer
+# sync_direction :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# external_token :string(255)
+# webhook_gid :integer
+#
+# Indexes
+#
+# index_sync_repositories_on_project_id (project_id)
+#
+
+class SyncRepository < ApplicationRecord
+
+ belongs_to :project
+ has_many :sync_repository_branches, dependent: :destroy
+
+ before_destroy :unbind_reposyncer
+
+ validates :repo_name, uniqueness: { message: "已存在" }
+
+ def unbind_reposyncer
+ Reposync::DeleteRepoService.call(self.repo_name) rescue nil
+ end
+
+end
diff --git a/app/models/sync_repository_branch.rb b/app/models/sync_repository_branch.rb
new file mode 100644
index 000000000..1940fd116
--- /dev/null
+++ b/app/models/sync_repository_branch.rb
@@ -0,0 +1,38 @@
+# == Schema Information
+#
+# Table name: sync_repository_branches
+#
+# id :integer not null, primary key
+# sync_repository_id :integer
+# gitlink_branch_name :string(255)
+# external_branch_name :string(255)
+# sync_time :datetime
+# sync_status :integer default("0")
+# reposync_branch_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# enable :boolean default("1")
+#
+# Indexes
+#
+# index_sync_repository_branches_on_sync_repository_id (sync_repository_id)
+#
+
+class SyncRepositoryBranch < ApplicationRecord
+
+ belongs_to :sync_repository
+
+ before_destroy :unbind_reposyncer
+
+ enum sync_status: {success: 1, failure: 2}
+
+
+ def unbind_reposyncer
+ if self.sync_repository.sync_direction.to_i == 1
+ Reposync::DeleteBranchService.call(self.sync_repository&.repo_name, self.gitlink_branch_name) rescue nil
+ else
+ Reposync::DeleteBranchService.call(self.sync_repository&.repo_name, self.external_branch_name) rescue nil
+ end
+ end
+
+end
diff --git a/app/models/template_message_setting/create_or_assign.rb b/app/models/template_message_setting/create_or_assign.rb
index 629051305..8f0817070 100644
--- a/app/models/template_message_setting/create_or_assign.rb
+++ b/app/models/template_message_setting/create_or_assign.rb
@@ -28,5 +28,6 @@ class TemplateMessageSetting::CreateOrAssign < TemplateMessageSetting
self.find_or_create_by(name: "疑修状态变更", key: "IssueChanged")
self.find_or_create_by(name: "合并请求状态变更", key: "PullRequestChanged")
self.find_or_create_by(name: "疑修截止日期到达最后一天", key: "IssueExpire", notification_disabled: false)
+ self.find_or_create_by(name: "里程碑逾期提醒", key: "MilestoneExpired", notification_disabled: false, email_disabled: true)
end
end
diff --git a/app/models/token.rb b/app/models/token.rb
index fac516eb8..7d65f32a3 100644
--- a/app/models/token.rb
+++ b/app/models/token.rb
@@ -1,19 +1,19 @@
-# == Schema Information
-#
-# Table name: tokens
-#
-# id :integer not null, primary key
-# user_id :integer default("0"), not null
-# action :string(30) default(""), not null
-# value :string(40) default(""), not null
-# created_on :datetime not null
-#
-# Indexes
-#
-# index_tokens_on_user_id (user_id)
-# tokens_value (value) UNIQUE
-#
-
+# == Schema Information
+#
+# Table name: tokens
+#
+# id :integer not null, primary key
+# user_id :integer default("0"), not null
+# action :string(30) default(""), not null
+# value :string(40) default(""), not null
+# created_on :datetime not null
+#
+# Indexes
+#
+# index_tokens_on_user_id (user_id)
+# tokens_value (value) UNIQUE
+#
+
#
# This program is free software; you can redistribute it and/or
@@ -44,7 +44,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 +117,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 8ffa5b181..9d623f949 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -115,7 +115,7 @@ class User < Owner
# trustie: 来自Trustie平台
# forge: 平台本身注册的用户
# military: 军科的用户
- enumerize :platform, in: [:forge, :educoder, :trustie, :military, :github, :gitee, :qq, :wechat, :bot], default: :forge, scope: :shallow
+ enumerize :platform, in: [:forge, :educoder, :trustie, :military, :github, :gitee, :qq, :wechat, :bot, :acge], default: :forge, scope: :shallow
belongs_to :laboratory, optional: true
has_one :user_extension, dependent: :destroy
@@ -465,7 +465,7 @@ class User < Owner
$gitea_client.delete_users_tokens_by_username_token(self.login, e["name"], {query: {sudo: self.login} })
}
end
- new_result = $gitea_client.post_users_tokens_by_username(self.login, { query: {sudo: self.login}, body:{ name: self.login} })
+ new_result = $gitea_client.post_users_tokens_by_username(self.login, { query: {sudo: self.login}, body:{ name: "#{self.login}-#{SecureRandom.hex(6)}", scopes: ["all"]}.to_json })
if new_result["sha1"].present?
update(gitea_token: new_result["sha1"])
end
diff --git a/app/models/user_template_message_setting.rb b/app/models/user_template_message_setting.rb
index 49db51d4d..befd2be87 100644
--- a/app/models/user_template_message_setting.rb
+++ b/app/models/user_template_message_setting.rb
@@ -36,6 +36,7 @@ class UserTemplateMessageSetting < ApplicationRecord
"CreateOrAssign::IssueChanged": true,
"CreateOrAssign::PullRequestChanged": true,
"CreateOrAssign::IssueExpire": true,
+ "CreateOrAssign::MilestoneExpired": true,
"ManageProject::Issue": true,
"ManageProject::PullRequest": true,
"ManageProject::Member": true,
@@ -44,6 +45,8 @@ class UserTemplateMessageSetting < ApplicationRecord
"ManageProject::Forked": true,
"ManageProject::Milestone": true,
"ManageProject::MilestoneCompleted": true,
+ "ManageProject::MilestoneExpired": true,
+ "ManageProject::MilestoneEarlyExpired": true,
}.stringify_keys!
end
@@ -57,6 +60,7 @@ class UserTemplateMessageSetting < ApplicationRecord
"CreateOrAssign::IssueChanged": false,
"CreateOrAssign::PullRequestChanged": false,
"CreateOrAssign::IssueExpire": false,
+ "CreateOrAssign::MilestoneExpired": false,
"ManageProject::Issue": false,
"ManageProject::PullRequest": false,
"ManageProject::Member": false,
@@ -65,6 +69,8 @@ class UserTemplateMessageSetting < ApplicationRecord
"ManageProject::Forked": false,
"ManageProject::Milestone": false,
"ManageProject::MilestoneCompleted": false,
+ "ManageProject::MilestoneExpired": false,
+ "ManageProject::MilestoneEarlyExpired": false,
}.stringify_keys!
end
diff --git a/app/models/version.rb b/app/models/version.rb
index ec2ad68cd..871f63dfd 100644
--- a/app/models/version.rb
+++ b/app/models/version.rb
@@ -68,5 +68,7 @@ class Version < ApplicationRecord
def send_update_message_to_notice_system
SendTemplateMessageJob.perform_later('ProjectMilestoneCompleted', self.id) if Site.has_notice_menu? && self.issue_percent == 1.0
+ SendTemplateMessageJob.perform_later('ProjectMilestoneEarlyExpired', self.id) if Site.has_notice_menu? && self.effective_date == Date.today + 1.days
+ SendTemplateMessageJob.perform_later('ProjectMilestoneExpired', self.id) if Site.has_notice_menu? && self.effective_date == Date.today - 1.days
end
end
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..3f92963a3
--- /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.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/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..02b2b533c 100644
--- a/app/services/api/v1/issues/delete_service.rb
+++ b/app/services/api/v1/issues/delete_service.rb
@@ -19,11 +19,11 @@ class Api::V1::Issues::DeleteService < ApplicationService
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 +37,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 b267647e7..c27dcd29f 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] , 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 = issues.distinct.size
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 d55c0f586..895c98c5c 100644
--- a/app/services/api/v1/issues/update_service.rb
+++ b/app/services/api/v1/issues/update_service.rb
@@ -5,7 +5,8 @@ class Api::V1::Issues::UpdateService < ApplicationService
attr_reader :project, :issue, :current_user
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
@@ -30,6 +31,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 +47,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?
@@ -68,28 +76,37 @@ 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?
+ @updated_issue.pm_issue_type = @pm_issue_type unless @pm_issue_type.nil?
+ @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_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
@@ -135,7 +152,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
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", "description").symbolize_keys)
+ @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
diff --git a/app/services/api/v1/projects/actions/runs/job_show_service.rb b/app/services/api/v1/projects/actions/runs/job_show_service.rb
new file mode 100644
index 000000000..e80e882be
--- /dev/null
+++ b/app/services/api/v1/projects/actions/runs/job_show_service.rb
@@ -0,0 +1,42 @@
+class Api::V1::Projects::Actions::Runs::JobShowService < ApplicationService
+ include ActiveModel::Model
+
+ attr_reader :project, :token, :owner, :repo, :run, :job, :log_cursors
+ attr_accessor :gitea_data
+
+ validates :run, :job, :log_cursors, presence: true
+
+ def initialize(project, run, job, log_cursors, token = nil)
+ @project = project
+ @owner = project&.owner.login
+ @repo = project&.identifier
+ @run = run
+ @job = job
+ @log_cursors = log_cursors
+ @token = token
+ end
+
+ def call
+ raise Error, errors.full_messages.join(",") unless valid?
+ load_gitea_data
+
+ @gitea_data
+ end
+
+ private
+ def request_params
+ {
+ access_token: token
+ }
+ end
+
+ def request_body
+ {
+ logCursors: log_cursors
+ }
+ end
+
+ def load_gitea_data
+ @gitea_data = $gitea_hat_client.post_repos_actions_runs_jobs_by_owner_repo_run_job(owner, repo, run, job, {query: request_params, body: request_body.to_json})
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/actions/runs/list_service.rb b/app/services/api/v1/projects/actions/runs/list_service.rb
new file mode 100644
index 000000000..5889518ce
--- /dev/null
+++ b/app/services/api/v1/projects/actions/runs/list_service.rb
@@ -0,0 +1,40 @@
+class Api::V1::Projects::Actions::Runs::ListService < ApplicationService
+ include ActiveModel::Model
+
+ attr_reader :project, :token, :owner, :repo, :workflow, :page, :limit
+ attr_accessor :gitea_data
+
+ validates :workflow, presence: true
+
+ def initialize(project, params, token =nil)
+ @project = project
+ @owner = project&.owner.login
+ @repo = project&.identifier
+ @workflow = params[:workflow]
+ @page = params[:page] || 1
+ @limit = params[:limit] || 15
+ @token = token
+ end
+
+ def call
+ raise Error, errors.full_messages.join(",") unless valid?
+ load_gitea_data
+
+ @gitea_data
+ end
+
+ private
+ def request_params
+ {
+ access_token: token,
+ workflow: workflow,
+ page: page,
+ limit: limit
+ }
+ end
+
+ def load_gitea_data
+ @gitea_data = $gitea_hat_client.get_repos_actions_by_owner_repo(owner, repo, {query: request_params}) rescue nil
+ raise Error, '获取流水线执行记录失败!' unless @gitea_data.is_a?(Hash)
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/branches/delete_service.rb b/app/services/api/v1/projects/branches/delete_service.rb
index 28836c797..341079273 100644
--- a/app/services/api/v1/projects/branches/delete_service.rb
+++ b/app/services/api/v1/projects/branches/delete_service.rb
@@ -32,7 +32,7 @@ class Api::V1::Projects::Branches::DeleteService < ApplicationService
def excute_data_to_gitea
begin
- @gitea_data = $gitea_client.delete_repos_branches_by_owner_repo_branch(owner, repo, branch_name, {query: request_params})
+ @gitea_data = $gitea_client.delete_repos_branches_by_owner_repo_branch(owner, repo, CGI.escape(branch_name), {query: request_params})
rescue => e
raise Error, '保护分支无法删除!' if e.to_s.include?("branch protected")
raise Error, '删除分支失败!'
diff --git a/app/services/api/v1/projects/branches/list_service.rb b/app/services/api/v1/projects/branches/list_service.rb
index 590c4884f..6980b71ea 100644
--- a/app/services/api/v1/projects/branches/list_service.rb
+++ b/app/services/api/v1/projects/branches/list_service.rb
@@ -1,6 +1,6 @@
class Api::V1::Projects::Branches::ListService < ApplicationService
- attr_accessor :project, :token, :owner, :repo, :name, :page, :limit
+ attr_accessor :project, :token, :owner, :repo, :name, :state, :page, :limit
attr_accessor :gitea_data, :gitea_repo_data
def initialize(project, params, token=nil)
@@ -9,6 +9,7 @@ class Api::V1::Projects::Branches::ListService < ApplicationService
@repo = project&.identifier
@token = token
@name = params[:name]
+ @state = params[:state]
@page = params[:page]
@limit = params[:limit]
end
@@ -18,7 +19,6 @@ class Api::V1::Projects::Branches::ListService < ApplicationService
load_default_branch
@gitea_data[:default_branch] = @gitea_repo_data["default_branch"]
-
@gitea_data
end
@@ -30,7 +30,8 @@ class Api::V1::Projects::Branches::ListService < ApplicationService
limit: limit
}
params.merge!({name: name}) if name.present?
-
+ params.merge!({state: state}) if state.present?
+
params
end
diff --git a/app/services/api/v1/projects/branches/restore_service.rb b/app/services/api/v1/projects/branches/restore_service.rb
new file mode 100644
index 000000000..fbd6220ed
--- /dev/null
+++ b/app/services/api/v1/projects/branches/restore_service.rb
@@ -0,0 +1,47 @@
+class Api::V1::Projects::Branches::RestoreService < ApplicationService
+
+ include ActiveModel::Model
+
+ attr_accessor :project, :token, :owner, :repo, :branch_id, :branch_name
+ attr_accessor :gitea_data
+
+ validates :branch_id, :branch_name, presence: true
+
+ def initialize(project, branch_id, branch_name, token= nil)
+ @project = project
+ @owner = project&.owner&.login
+ @repo = project&.identifier
+ @branch_id = branch_id
+ @branch_name = branch_name
+ @token = token
+ end
+
+ def call
+ raise Error, errors.full_messages.join(",") unless valid?
+ excute_data_to_gitea
+
+ true
+ end
+
+ private
+ def request_params
+ {
+ access_token: token
+ }
+ end
+
+ def request_body
+ {
+ branch_id: branch_id,
+ name: branch_name,
+ }
+ end
+
+ def excute_data_to_gitea
+ begin
+ @gitea_data = $gitea_hat_client.post_repos_branches_restore_by_owner_repo(owner, repo, {query: request_params, body: request_body.to_json})
+ rescue => e
+ raise Error, '恢复分支失败!'
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/commits/recent_service.rb b/app/services/api/v1/projects/commits/recent_service.rb
new file mode 100644
index 000000000..9a226d9f2
--- /dev/null
+++ b/app/services/api/v1/projects/commits/recent_service.rb
@@ -0,0 +1,44 @@
+class Api::V1::Projects::Commits::RecentService < ApplicationService
+
+ attr_reader :project, :page, :limit, :keyword, :owner, :repo, :token
+ attr_accessor :gitea_data, :gitea_repo_detail
+
+ def initialize(project, params, token=nil)
+ @project = project
+ @page = params[:page] || 1
+ @limit = params[:limit] || 15
+ @keyword = params[:keyword]
+ @owner = project&.owner&.login
+ @repo = project&.identifier
+ @token = token
+ end
+
+ def call
+ load_gitea_data
+ load_gitea_repo_detail
+
+ {result: gitea_data, detail:gitea_repo_detail}
+ end
+
+ private
+ def request_params
+ param = {
+ access_token: token,
+ page: page,
+ limit: limit
+ }
+ param.merge!(keyword: keyword) if keyword.present?
+
+ param
+ end
+
+ def load_gitea_data
+ @gitea_data = $gitea_hat_client.get_repos_recent_commits_by_owner_repo(owner, repo, {query: request_params}) rescue nil
+ raise Error, "获取最近提交列表失败" unless @gitea_data.is_a?(Hash)
+ end
+
+ def load_gitea_repo_detail
+ @gitea_repo_detail = $gitea_client.get_repos_by_owner_repo(owner, repo, {query: {access_token: token}})
+ raise Error, "获取项目详情失败" unless @gitea_repo_detail.is_a?(Hash)
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/compare_service.rb b/app/services/api/v1/projects/compare_service.rb
index 23a248305..9e646326f 100644
--- a/app/services/api/v1/projects/compare_service.rb
+++ b/app/services/api/v1/projects/compare_service.rb
@@ -29,6 +29,6 @@ class Api::V1::Projects::CompareService < ApplicationService
end
def load_gitea_data
- @gitea_data = $gitea_client.get_repos_compare_by_owner_repo_from_to(owner, repo, from, to, {query: request_params}) rescue nil
+ @gitea_data = $gitea_hat_client.get_repos_compare_by_owner_repo_baseref_headref(owner, repo, to, from, {query: request_params}) rescue nil
end
end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/sync_repositories/create_service.rb b/app/services/api/v1/projects/sync_repositories/create_service.rb
new file mode 100644
index 000000000..4a0dc5091
--- /dev/null
+++ b/app/services/api/v1/projects/sync_repositories/create_service.rb
@@ -0,0 +1,109 @@
+class Api::V1::Projects::SyncRepositories::CreateService < ApplicationService
+
+ include ActiveModel::Model
+
+ attr_reader :project, :type, :external_token, :external_repo_address, :sync_granularity, :external_branch_name, :gitlink_branch_name, :first_sync_direction
+ attr_accessor :sync_repository1, :sync_repository2, :sync_repository_branch1, :sync_repository_branch2, :gitea_webhook
+
+ validates :type, inclusion: {in: %w(SyncRepositories::Gitee SyncRepositories::Github)}
+ validates :external_repo_address, format: { with: CustomRegexp::URL_REGEX, multiline: true, message: "地址格式不正确" }
+ validates :sync_granularity, :first_sync_direction, inclusion: {in: [1,2]}
+ # validate :check_gitlink_branch_name
+
+ def initialize(project, params)
+ @project = project
+ @type = params[:type]
+ @external_token = params[:external_token]
+ @external_repo_address = params[:external_repo_address]
+ @sync_granularity = params[:sync_granularity].to_i
+ @external_branch_name = params[:external_branch_name]
+ @gitlink_branch_name = params[:gitlink_branch_name]
+ @first_sync_direction = params[:first_sync_direction].to_i
+ end
+
+ def call
+ raise Error, errors.full_messages.join(",") unless valid?
+
+ create_webhook
+ if sync_granularity == 2
+ # 创建两个不同方向的同步仓库
+ create_sync_repository
+ # 创建两个不同方向的同步分支
+ create_sync_repository_branch
+ # 第一次同步
+ touch_first_sync_branch
+ else
+ create_sync_repository
+ touch_first_sync
+ end
+
+ [@sync_repository1, @sync_repository2, @sync_repository_branch1, @sync_repository_branch2]
+ end
+
+ # def check_gitlink_branch_name
+ # if sync_granularity == 2
+ # result = $gitea_hat_client.get_repos_branch_name_set_by_owner_repo(project&.owner&.login, project&.identifier) rescue nil
+ # raise Error, '分支不存在' if !result.include?(gitlink_branch_name)
+ # end
+ # end
+
+ private
+ def create_sync_repository
+ repository1 = Reposync::CreateSyncRepoService.call(repo_name(1), gitlink_repo_address, gitlink_token, external_repo_address, external_token, sync_granularity, 1)
+ repository2 = Reposync::CreateSyncRepoService.call(repo_name(2), gitlink_repo_address, gitlink_token, external_repo_address, external_token, sync_granularity, 2)
+ raise Error, '创建同步仓库失败' if repository1[0].to_i > 0 || repository2[0].to_i > 0
+ @sync_repository1 = SyncRepository.create!(project: project, type: type, repo_name: repo_name(1), external_repo_address: external_repo_address, external_token: external_token, sync_granularity: sync_granularity, sync_direction: 1, webhook_gid: @gitea_webhook["id"])
+ @sync_repository2 = SyncRepository.create!(project: project, type: type, repo_name: repo_name(2), external_repo_address: external_repo_address, external_token: external_token, sync_granularity: sync_granularity, sync_direction: 2, webhook_gid: @gitea_webhook["id"])
+ end
+
+ def create_sync_repository_branch
+ branch1 = Reposync::CreateSyncBranchService.call(repo_name(1),gitlink_branch_name, external_branch_name)
+ branch2 = Reposync::CreateSyncBranchService.call(repo_name(2),gitlink_branch_name, external_branch_name)
+ raise Error, '创建同步仓库分支失败' if branch1[0].to_i > 0 || branch2[0].to_i > 0
+ @sync_repository_branch1 = SyncRepositoryBranch.create!(sync_repository: @sync_repository1, gitlink_branch_name: gitlink_branch_name, external_branch_name: external_branch_name, reposync_branch_id: branch1[1]["id"])
+ @sync_repository_branch2 = SyncRepositoryBranch.create!(sync_repository: @sync_repository2, gitlink_branch_name: gitlink_branch_name, external_branch_name: external_branch_name, reposync_branch_id: branch2[1]["id"])
+ end
+
+ def touch_first_sync
+ first_sync_direction == 1 ? TouchSyncJob.perform_later(@sync_repository1) : TouchSyncJob.perform_later(@sync_repository2)
+ end
+
+ def touch_first_sync_branch
+ first_sync_direction == 1 ? TouchSyncJob.perform_later(@sync_repository_branch1) : TouchSyncJob.perform_later(@sync_repository_branch2)
+ end
+
+ def create_webhook
+ url = ""
+ if type == "SyncRepositories::Gitee"
+ url = "#{Rails.application.config_for(:configuration)['platform_url']}/api/v1/#{project&.owner&.login}/#{project&.identifier}/sync_repositories/sync?sync_direction=1&repo_type=SyncRepositories::Gitee"
+ else
+ url = "#{Rails.application.config_for(:configuration)['platform_url']}/api/v1/#{project&.owner&.login}/#{project&.identifier}/sync_repositories/sync?sync_direction=1&repo_type=SyncRepositories::Github"
+ end
+ webhook_params = {
+ active: true,
+ branch_filter: '*',
+ http_method: 'POST',
+ url: url,
+ content_type: 'json',
+ type: 'reposync',
+ events: ["push"]
+ }
+ @gitea_webhook = Api::V1::Projects::Webhooks::CreateService.call(project, webhook_params)
+ end
+
+ def repo_name(sync_direction)
+ if type == "SyncRepositories::Gitee"
+ return "gitee:#{project.id}:#{sync_granularity}:#{sync_direction}"
+ else
+ return "github:#{project.id}:#{sync_granularity}:#{sync_direction}"
+ end
+ end
+
+ def gitlink_repo_address
+ "#{EduSetting.get("gitlink_repo_domain")}/#{project.owner&.login}/#{project.identifier}.git"
+ end
+
+ def gitlink_token
+ EduSetting.get("gitlink_admin_token")
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/sync_repositories/update_service.rb b/app/services/api/v1/projects/sync_repositories/update_service.rb
new file mode 100644
index 000000000..9b4fec3ec
--- /dev/null
+++ b/app/services/api/v1/projects/sync_repositories/update_service.rb
@@ -0,0 +1,40 @@
+class Api::V1::Projects::SyncRepositories::UpdateService < ApplicationService
+
+ include ActiveModel::Model
+ attr_reader :project, :external_token, :external_repo_address, :sync_repositories
+ attr_accessor :sync_repository1, :sync_repository2
+
+ validates :external_repo_address, format: { with: CustomRegexp::URL_REGEX, multiline: true, message: "地址格式不正确" }
+ validates :external_token, presence: true
+
+ #Api::V1::Projects::SyncRepositories::UpdateService.call(Project.last, "21,22", {external_repo_address: "https://github.com/viletyy/testdevops.git", external_token:"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"})
+ def initialize(project, sync_repository_ids, params)
+ @project = project
+ @sync_repositories = SyncRepository.where(project_id: project.id, id: sync_repository_ids.split(","))
+ @external_token = params[:external_token]
+ @external_repo_address = params[:external_repo_address]
+ end
+
+ def call
+ raise Error, errors.full_messages.join(",") unless valid?
+
+ update_sync_repository
+
+ end
+
+ private
+ def update_sync_repository
+ @sync_repositories.each do |repo|
+ Reposync::UpdateRepoAddrService.call(repo&.repo_name, internal_repo_address, internal_token, external_repo_address, external_token)
+ repo.update_attributes!({external_repo_address: external_repo_address, external_token: external_token})
+ end
+ end
+
+ def internal_repo_address
+ "#{EduSetting.get("gitlink_repo_domain")}/#{project.owner&.login}/#{project.identifier}.git"
+ end
+
+ def internal_token
+ EduSetting.get("gitlink_admin_token")
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/projects/tags/delete_service.rb b/app/services/api/v1/projects/tags/delete_service.rb
index d0d317aa8..8f898bf1a 100644
--- a/app/services/api/v1/projects/tags/delete_service.rb
+++ b/app/services/api/v1/projects/tags/delete_service.rb
@@ -32,7 +32,7 @@ class Api::V1::Projects::Tags::DeleteService < ApplicationService
def excute_data_to_gitea
begin
- @gitea_data = $gitea_client.delete_repos_tags_by_owner_repo_tag(owner, repo, tag_name, {query: request_params})
+ @gitea_data = $gitea_client.delete_repos_tags_by_owner_repo_tag(owner, repo, CGI.escape(tag_name), {query: request_params})
rescue => e
raise Error, '请先删除发行版!' if e.to_s.include?("409")
raise Error, '删除标签失败!'
diff --git a/app/services/api/v1/projects/webhooks/create_service.rb b/app/services/api/v1/projects/webhooks/create_service.rb
index 303f3b39f..829710cbe 100644
--- a/app/services/api/v1/projects/webhooks/create_service.rb
+++ b/app/services/api/v1/projects/webhooks/create_service.rb
@@ -8,7 +8,7 @@ class Api::V1::Projects::Webhooks::CreateService < ApplicationService
validates :active, inclusion: {in: [true, false]}
validates :http_method, inclusion: { in: %w(POST GET), message: "请输入正确的请求方式"}
validates :content_type, inclusion: { in: %w(json form), message: "请输入正确的Content Type"}
- validates :type, inclusion: {in: %w(gitea slack discord dingtalk telegram msteams feishu matrix jianmu softbot), message: "请输入正确的Webhook Type"}
+ validates :type, inclusion: {in: %w(gitea slack discord dingtalk telegram msteams feishu matrix jianmu softbot reposync), message: "请输入正确的Webhook Type"}
def initialize(project, params, token=nil)
@project = project
@owner = project&.owner.login
diff --git a/app/services/baidu/tongji_service.rb b/app/services/baidu/tongji_service.rb
new file mode 100644
index 000000000..a2a4f28fc
--- /dev/null
+++ b/app/services/baidu/tongji_service.rb
@@ -0,0 +1,221 @@
+module Baidu
+ class TongjiService < ApplicationService
+ attr_reader :client_id, :client_secret, :site_id
+ # login、code、password、password_confirmation
+ def initialize
+ @client_id = "6dMO2kqKUaMZkBrMaUMxQSNAT49v0Mjq"
+ @client_secret = "qvWqF33AOmGs1tPCgsROvis9EQCuNmd3"
+ @site_id = 18657013
+ end
+
+ def call
+
+ end
+
+
+ def init_overview_data_by(start_date = nil, end_date = nil)
+ start_date = Time.now.prev_year.beginning_of_year if start_date.nil?
+ end_date = Time.now
+ Rails.logger.info("*********开始百度统计-概览:#{start_date}-#{end_date}*********")
+ sql_connection = ActiveRecord::Base.connection
+ sql_connection.begin_db_transaction
+
+ # 如果存在数据 先清空
+ # sql_connection.execute("delete from daily_platform_statistics where date between '#{start_date}' and '#{end_date}'")
+ multiple_days_data = overview_multiple_days_data(start_date, end_date)
+ if multiple_days_data.present?
+ sql = "replace into daily_platform_statistics (date,pv,visitor,ip,created_at,updated_at) values #{multiple_days_data.join(",")}"
+ sql_connection.execute(sql)
+ end
+ sql_connection.commit_db_transaction
+ Rails.logger.info("*********结束百度统计-概览:#{start_date}-#{end_date}*********")
+ end
+
+ def init_source_from_data_by(start_date = nil, end_date = nil)
+ start_date = Time.now.prev_year.beginning_of_year if start_date.nil?
+ end_date = Time.now
+ Rails.logger.info("*********开始百度统计-来源:#{start_date}-#{end_date}*********")
+ source_from_batch_add(start_date, end_date)
+ Rails.logger.info("*********结束百度统计-来源:#{start_date}-#{end_date}*********")
+ end
+
+ # 按日期获取来源数据
+ def source_from_batch_add(start_date,end_date)
+ # 补充更新开始时间的当天数据
+ source_from_by_date(start_date)
+ diff_days(start_date, end_date).times.each do |t|
+ new_start_date = start_date + (t + 1).days
+ source_from_by_date(new_start_date)
+ end
+ # 补充更新最后时间一天数据
+ source_from_by_date(end_date)
+ end
+
+ # 按天获取来源数据
+ def source_from_by_date(start_date)
+ return [] unless access_token.present? && start_date.present?
+ source_from_data = api("source/all/a", start_date, start_date, "pv_count,visitor_count,ip_count")
+ source_from = []
+ source_from_data['items'][1].each_with_index do |source, index|
+ source_from.push(((source[0].to_f / source_from_data['sum'][0][0].to_f) * 100).round(2))
+ end
+ daily_statistic = DailyPlatformStatistic.find_or_initialize_by(date: start_date)
+ daily_statistic.source_through = source_from[0]
+ daily_statistic.source_link = source_from[1]
+ daily_statistic.source_search = source_from[2]
+ daily_statistic.source_custom = source_from[3]
+ daily_statistic.save
+ end
+
+ def diff_days(start_date, end_date)
+ (end_date.beginning_of_day.to_i - start_date.beginning_of_day.to_i) / (24 * 3600)
+ end
+
+ def overview_batch_add(start_date, end_date)
+ return [] unless access_token.present? && start_date.present? && end_date.present?
+ start_date = Time.now - 1.days if start_date.strftime("%Y%m%d") == end_date.strftime("%Y%m%d")
+ overview_data = api("overview/getTimeTrendRpt", start_date, end_date, "pv_count,visitor_count,ip_count")
+ overview_data['items'][0].each_with_index do |date, index|
+ pv = overview_data['items'][1][index][0]
+ visitor = overview_data['items'][1][index][1]
+ ip = overview_data['items'][1][index][2]
+ job_date = date[0].to_s.gsub("/", "-")
+ daily_statistic = DailyPlatformStatistic.find_or_initialize_by(date: job_date)
+ daily_statistic.date = job_date
+ daily_statistic.pv = pv
+ daily_statistic.visitor = visitor
+ daily_statistic.ip = ip
+ daily_statistic.save
+ end
+ overview_data
+ end
+
+ def overview_multiple_days_data(start_date, end_date)
+ return [] unless access_token.present? && start_date.present? && end_date.present?
+ overview_data = api("overview/getTimeTrendRpt", start_date, end_date, "pv_count,visitor_count,ip_count")
+ data = []
+ created_at = Time.now.strftime("%Y-%m-%d 00:00:00")
+ overview_data['items'][0].each_with_index do |date, index|
+ pv = overview_data['items'][1][index][0]
+ visitor = overview_data['items'][1][index][1]
+ ip = overview_data['items'][1][index][2]
+ data.push("('#{date[0].to_s.gsub("/", "-")}', #{pv.to_s.gsub("--","0")}, #{visitor.to_s.gsub("--","0")}, #{ip.to_s.gsub("--","0")},\"#{created_at}\",\"#{created_at}\")")
+ end
+ data
+ end
+
+ def code_url
+ "http://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id=#{client_id}&redirect_uri=oob&scope=basic&display=popup"
+ end
+
+ def oauth_url(code)
+ "http://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code&code=#{code}&client_id=#{client_id}&client_secret=#{client_secret}&redirect_uri=oob"
+ end
+
+ def get_access_token(code)
+ uri = URI.parse(oauth_url(code))
+ response = Net::HTTP.get_response(uri)
+ Rails.logger.info "baidu_tongji_auth response.body ===== #{response.body}"
+ if response.code.to_i == 200
+ data = JSON.parse(response.body)
+ access_token = data['access_token']
+ refresh_token = data['refresh_token']
+ expires_in = data['expires_in']
+ if access_token.present?
+ Rails.cache.write("baidu_tongji_auth/access_token", access_token, expires_in: expires_in)
+ Rails.cache.write("baidu_tongji_auth/refresh_token", refresh_token, expires_in: 1.year)
+ end
+ end
+ end
+
+ def refresh_access_token
+ url = "http://openapi.baidu.com/oauth/2.0/token?grant_type=refresh_token&refresh_token=#{refresh_token}&client_id=#{client_id}&client_secret=#{client_secret}"
+ uri = URI.parse(url)
+ response = Net::HTTP.get_response(uri)
+ Rails.logger.info "baidu_tongji_auth response.body ===== #{response.body}"
+ if response.code.to_i == 200
+ data = JSON.parse(response.body)
+ access_token = data['access_token']
+ refresh_token = data['refresh_token']
+ expires_in = data['expires_in']
+ if access_token.present?
+ Rails.cache.write("baidu_tongji_auth/access_token", access_token, expires_in: expires_in)
+ Rails.cache.write("baidu_tongji_auth/refresh_token", refresh_token, expires_in: 1.year)
+ end
+ end
+ end
+
+ def access_token
+ access_token = Rails.cache.read("baidu_tongji_auth/access_token")
+ if access_token.blank? && refresh_token.present?
+ refresh_access_token
+ access_token = Rails.cache.read("baidu_tongji_auth/access_token")
+ end
+ access_token
+ end
+
+ def refresh_token
+ refresh_token = Rails.cache.read("baidu_tongji_auth/refresh_token")
+ # 如果刷新token失效,access_token也重置
+ if refresh_token.blank?
+ Rails.cache.delete("baidu_tongji_auth/access_token")
+ end
+ refresh_token
+ end
+
+ # 网站概况(趋势数据)
+ def api_overview
+ start_date = Time.now.beginning_of_week
+ end_date = Time.now
+ start_date = Time.now - 1.days if start_date.strftime("%Y%m%d") == end_date.strftime("%Y%m%d")
+ api("overview/getTimeTrendRpt", start_date, end_date, "pv_count,visitor_count,ip_count")
+ end
+
+ # 网站概况(来源网站、搜索词、入口页面、受访页面)
+ def api_overview_getCommonTrackRpt
+ start_date = Time.now.beginning_of_week
+ end_date = Time.now
+ api("overview/getCommonTrackRpt", start_date, end_date, "pv_count")
+ end
+
+ # 全部来源
+ def source_from
+ start_date = Time.now.beginning_of_week
+ end_date = Time.now
+ api("source/all/a", start_date, end_date, "pv_count,visitor_count,ip_count")
+ end
+
+ def api(api_method, start_date, end_date, metrics = nil)
+ start_date_fmt = start_date.strftime("%Y%m%d")
+ end_date_fmt = end_date.strftime("%Y%m%d")
+ api_url = "https://openapi.baidu.com/rest/2.0/tongji/report/getData?access_token=#{access_token}&site_id=#{site_id}&method=#{api_method}&start_date=#{start_date_fmt}&end_date=#{end_date_fmt}&metrics=#{metrics}"
+ data = url_http_post(api_url, {})
+ data['result']
+ end
+
+ def url_http_post(api_url, params)
+ Rails.logger.info "api_url==#{api_url}"
+ uri = URI.parse(api_url)
+ http = Net::HTTP.new uri.host, uri.port
+ http.open_timeout = 60
+ http.read_timeout = 60
+ if uri.scheme == 'https'
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ http.use_ssl = true
+ end
+ begin
+ request = Net::HTTP::Post.new(uri)
+ request.set_form_data(params) if params.present?
+ request['Content-Type'] = 'application/json;charset=utf-8'
+ # request['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
+ response = http.start { |http| http.request(request) }
+ Rails.logger.info "api response.body==#{response.body}"
+ JSON.parse response.body
+ rescue => err
+ Rails.logger.error("#############api_url:#{api_url},error:#{err.message.size}")
+ # Rails.logger.error("#############api_url:#{api_url},error:#{err.message}")
+ return {}
+ end
+ end
+ end
+end
diff --git a/app/services/cache/v2/project_common_service.rb b/app/services/cache/v2/project_common_service.rb
index 0d167c2a7..1e85f3e08 100644
--- a/app/services/cache/v2/project_common_service.rb
+++ b/app/services/cache/v2/project_common_service.rb
@@ -1,5 +1,5 @@
class Cache::V2::ProjectCommonService < ApplicationService
- attr_reader :project_id, :owner_id, :name, :identifier, :description, :visits, :watchers, :praises, :forks, :issues, :pullrequests, :commits
+ attr_reader :project_id, :owner_id, :name, :identifier, :description, :visits, :watchers, :praises, :forks, :issues, :closed_issues, :pullrequests, :commits
attr_accessor :project
def initialize(project_id, params={})
@@ -13,6 +13,7 @@ class Cache::V2::ProjectCommonService < ApplicationService
@praises = params[:praises]
@forks = params[:forks]
@issues = params[:issues]
+ @closed_issues = params[:closed_issues]
@pullrequests = params[:pullrequests]
@commits = params[:commits]
end
@@ -78,6 +79,10 @@ class Cache::V2::ProjectCommonService < ApplicationService
"issues"
end
+ def closed_issues_key
+ "closed_issues"
+ end
+
def pullrequests_key
"pullrequests"
end
@@ -151,6 +156,10 @@ class Cache::V2::ProjectCommonService < ApplicationService
Cache::V2::ProjectRankService.call(@project_id, {issues: @issues})
Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {issues: @issues})
end
+ if @closed_issues.present?
+ $redis_cache.hincrby(project_common_key, closed_issues_key, @closed_issues)
+ Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {closed_issues: @closed_issues})
+ end
if @pullrequests.present?
$redis_cache.hincrby(project_common_key, pullrequests_key, @pullrequests)
Cache::V2::ProjectRankService.call(@project_id, {pullrequests: @pullrequests})
@@ -202,6 +211,10 @@ class Cache::V2::ProjectCommonService < ApplicationService
$redis_cache.hset(project_common_key, issues_key, Issue.issue_issue.where(project_id: @project_id).count)
end
+ def reset_project_closed_issues
+ $redis_cache.hset(project_common_key, closed_issues_key, Issue.issue_issue.closed.where(project_id: @project_id).count)
+ end
+
def reset_project_pullrequests
$redis_cache.hset(project_common_key, pullrequests_key, PullRequest.where(project_id: @project_id).count)
end
@@ -224,6 +237,7 @@ class Cache::V2::ProjectCommonService < ApplicationService
reset_project_praises
reset_project_forks
reset_project_issues
+ reset_project_closed_issues
reset_project_pullrequests
reset_project_commits
diff --git a/app/services/cache/v2/project_date_rank_service.rb b/app/services/cache/v2/project_date_rank_service.rb
index 9df69bbb4..bf661f0a2 100644
--- a/app/services/cache/v2/project_date_rank_service.rb
+++ b/app/services/cache/v2/project_date_rank_service.rb
@@ -1,6 +1,6 @@
# 项目日活跃度计算存储
class Cache::V2::ProjectDateRankService < ApplicationService
- attr_reader :project_id, :rank_date, :visits, :praises, :forks, :issues, :pullrequests, :commits
+ attr_reader :project_id, :rank_date, :visits, :praises, :forks, :issues, :closed_issues, :pullrequests, :commits
attr_accessor :project_common
def initialize(project_id, rank_date=Date.today, params={})
@@ -11,6 +11,7 @@ class Cache::V2::ProjectDateRankService < ApplicationService
@praises = params[:praises]
@forks = params[:forks]
@issues = params[:issues]
+ @closed_issues = params[:closed_issues]
@pullrequests = params[:pullrequests]
@commits = params[:commits]
end
@@ -57,6 +58,9 @@ class Cache::V2::ProjectDateRankService < ApplicationService
$redis_cache.zincrby(project_rank_key, @issues.to_i * 5, @project_id)
$redis_cache.hincrby(project_rank_statistic_key, "issues", @issues.to_i)
end
+ if @closed_issues.present?
+ $redis_cache.hincrby(project_rank_statistic_key, "closed_issues", @closed_issues.to_i)
+ end
if @pullrequests.present?
$redis_cache.zincrby(project_rank_key, @pullrequests.to_i * 10, @project_id)
$redis_cache.hincrby(project_rank_statistic_key, "pullrequests", @pullrequests.to_i)
diff --git a/app/services/getway/cms/get_service.rb b/app/services/getway/cms/get_service.rb
index 6b7050e84..76c5f27e8 100644
--- a/app/services/getway/cms/get_service.rb
+++ b/app/services/getway/cms/get_service.rb
@@ -16,6 +16,6 @@ class Getway::Cms::GetService < Getway::ClientService
end
def url
- "/cms/doc/open/#{doc_id}".freeze
+ "/cms/doc/open/baseInfo/#{doc_id}".freeze
end
end
\ No newline at end of file
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/user/generate_token_service.rb b/app/services/gitea/user/generate_token_service.rb
index 946bd68ce..e2dd6b0f4 100644
--- a/app/services/gitea/user/generate_token_service.rb
+++ b/app/services/gitea/user/generate_token_service.rb
@@ -29,7 +29,7 @@ class Gitea::User::GenerateTokenService < Gitea::ClientService
end
def request_params
- { name: "#{@username}-#{token_name}" }
+ { name: "#{@username}-#{token_name}", scopes: ["all"] }
end
def token_name
diff --git a/app/services/page_service.rb b/app/services/page_service.rb
index 5c166e82c..c85fa394c 100644
--- a/app/services/page_service.rb
+++ b/app/services/page_service.rb
@@ -47,7 +47,7 @@ class PageService
repo_link = project.repository.url
repo = project.repository.identifier
branch = branch
- script_path =page.build_script_path
+ script_path = branch == "gh-pages" ? "files_build" : page.build_script_path
if script_path.present?
uri = URI.parse("http://gitlink.#{@deploy_domain}/gitlink_execute_script?key=#{@deploy_key}&script_path=#{script_path}&project_dir=#{project_dir}&repo=#{repo}&repo_link=#{repo_link}&branch=#{branch}&owner=#{owner}")
response = Net::HTTP.get_response(uri)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index c4f892f7f..a727cf916 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -26,9 +26,6 @@ class Projects::CreateService < ApplicationService
end
end
@project
- rescue => e
- puts "create project service error: #{e.message}"
- raise Error, e.message
end
private
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 37edf56ee..63475f2b4 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -18,6 +18,8 @@ class Projects::ForkService < ApplicationService
:license_id, :ignore_id, {repository: [:identifier, :hidden]}]
result = Gitea::Repository::ForkService.new(@project.owner, @target_owner, @project.identifier, @organization, @new_identifier).call
+ Rails.logger.info("##### ForkService #{@project.identifier} result======#{result}")
+ raise Error, 'fork失败' if result.blank? or result['id'].blank?
clone_project.owner = @target_owner
clone_project.forked_from_project_id = @project.id
clone_project.gpid = result['id']
@@ -41,7 +43,7 @@ class Projects::ForkService < ApplicationService
clone_project
end
rescue => e
- puts "clone project service error: #{e.message}"
+ Rails.logger.info "fork project service error: #{e.message}"
raise Error, e.message
end
diff --git a/app/services/repositories/create_service.rb b/app/services/repositories/create_service.rb
index e7ff8bd1d..800d5f420 100644
--- a/app/services/repositories/create_service.rb
+++ b/app/services/repositories/create_service.rb
@@ -33,18 +33,17 @@ class Repositories::CreateService < ApplicationService
end
repository
end
- rescue => e
- puts "create repository service error: #{e.message}"
- raise Error, e.message
end
private
def create_gitea_repository
if project.owner.is_a?(User)
- @gitea_repository = Gitea::Repository::CreateService.new(user.gitea_token, gitea_repository_params).call
+ # @gitea_repository = Gitea::Repository::CreateService.new(user.gitea_token, gitea_repository_params).call
+ @gitea_repository = $gitea_client.post_user_repos({query: {token: user.gitea_token}, body: gitea_repository_params.to_json})
elsif project.owner.is_a?(Organization)
- @gitea_repository = Gitea::Organization::Repository::CreateService.call(user.gitea_token, project.owner.login, gitea_repository_params)
+ # @gitea_repository = Gitea::Organization::Repository::CreateService.call(user.gitea_token, project.owner.login, gitea_repository_params)
+ @gitea_repository = $gitea_client.post_orgs_repos_by_org(project.owner.login, {query: {token: user.gitea_token}, body: gitea_repository_params.to_json})
end
end
@@ -67,7 +66,7 @@ class Repositories::CreateService < ApplicationService
end
def repository_params
- params.merge(project_id: project.id)
+ params.merge(project_id: project.id).except(:auto_init)
end
def gitea_repository_params
diff --git a/app/services/reposync/client_service.rb b/app/services/reposync/client_service.rb
new file mode 100644
index 000000000..a8ddc18bd
--- /dev/null
+++ b/app/services/reposync/client_service.rb
@@ -0,0 +1,115 @@
+class Reposync::ClientService < ApplicationService
+ attr_reader :url, :params
+
+ def initialize(options={})
+ @url = options[:url]
+ @params = options[:params]
+ end
+
+ def post(url, params={})
+ puts "[reposync][POST] request params: #{params}"
+ conn.post do |req|
+ req.url full_url(url)
+ req.body = params[:data].to_json
+ end
+ end
+
+ def get(url, params={})
+ puts "[reposync][GET] request params: #{params}"
+ conn.get do |req|
+ req.url full_url(url, 'get')
+ params.each_pair do |key, value|
+ req.params["#{key}"] = value
+ end
+ end
+ end
+
+ def delete(url, params={})
+ puts "[reposync][DELETE] request params: #{params}"
+ conn.delete do |req|
+ req.url full_url(url)
+ req.body = params[:data].to_json
+ end
+ end
+
+ def patch(url, params={})
+ puts "[reposync][PATCH] request params: #{params}"
+ conn.patch do |req|
+ req.url full_url(url)
+ req.body = params[:data].to_json
+ end
+ end
+
+ def put(url, params={})
+ puts "[reposync][PUT] request params: #{params}"
+ conn.put do |req|
+ req.url full_url(url)
+ req.body = params[:data].to_json
+ end
+ end
+
+ def conn
+ @client ||= begin
+ Faraday.new(url: domain) do |req|
+ req.request :url_encoded
+ req.headers['Content-Type'] = 'application/json'
+ req.adapter Faraday.default_adapter
+ req.options.timeout = 100 # open/read timeout in seconds
+ req.options.open_timeout = 10 # connection open timeout in seconds
+ end
+ end
+
+ @client
+ end
+
+ def domain
+ EduSetting.get("reposync_api_domain") || "http://106.75.110.152:50087"
+ end
+
+ def full_url(api_rest, action='post')
+ url = [domain, api_rest].join('').freeze
+ url = action === 'get' ? url : URI.escape(url)
+ url = URI.escape(url) unless url.ascii_only?
+ puts "[reposync] request url: #{url}"
+ return url
+ end
+
+ def log_error(status, body)
+ puts "[reposync] status: #{status}"
+ puts "[reposync] body: #{body}"
+ end
+
+ def render_response(response)
+ status = response.status
+ body = JSON.parse(response&.body)
+
+ log_error(status, body)
+
+ if status == 200
+ if body["code_status"].to_i == 0
+ return [body["code_status"], body["data"], body["msg"]]
+ else
+ puts "[reposync][ERROR] code: #{body["code_status"]}"
+ puts "[reposync][ERROR] message: #{body["msg"]}"
+ return [body["code_status"], body["data"], body["msg"]]
+ end
+ end
+ end
+
+ def render_list_response(response)
+ status = response.status
+ body = JSON.parse(response&.body)
+
+ log_error(status, body)
+
+ if status == 200
+ if body["code_status"].to_i == 0
+ return [body["code_status"], body["data"], body["total"], body["msg"]]
+ else
+ puts "[reposync][ERROR] code: #{body["code_status"]}"
+ puts "[reposync][ERROR] message: #{body["msg"]}"
+ return [body["code_status"], body["data"], body["total"], body["msg"]]
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/create_sync_branch_service.rb b/app/services/reposync/create_sync_branch_service.rb
new file mode 100644
index 000000000..2b417a7e5
--- /dev/null
+++ b/app/services/reposync/create_sync_branch_service.rb
@@ -0,0 +1,30 @@
+class Reposync::CreateSyncBranchService < Reposync::ClientService
+
+ attr_accessor :repo_name, :internal_branch_name, :external_branch_name, :enable
+
+ def initialize(repo_name, internal_branch_name, external_branch_name, enable=true)
+ @repo_name = repo_name
+ @internal_branch_name = internal_branch_name
+ @external_branch_name = external_branch_name
+ @enable = enable
+ end
+
+ def call
+ result = post(url, request_params)
+ response = render_response(result)
+ end
+
+ private
+ def request_params
+ Hash.new.merge(data: {
+ internal_branch_name: internal_branch_name,
+ external_branch_name: external_branch_name,
+ enable: enable
+ }.stringify_keys)
+ end
+
+ def url
+ "/cerobot/sync/#{repo_name}/branch".freeze
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/reposync/create_sync_repo_service.rb b/app/services/reposync/create_sync_repo_service.rb
new file mode 100644
index 000000000..0f0cc43ee
--- /dev/null
+++ b/app/services/reposync/create_sync_repo_service.rb
@@ -0,0 +1,38 @@
+class Reposync::CreateSyncRepoService < Reposync::ClientService
+
+ attr_accessor :repo_name, :internal_repo_address, :inter_token, :external_repo_address, :exter_token, :sync_granularity, :sync_direction, :enable
+
+ def initialize(repo_name, internal_repo_address, inter_token, external_repo_address, exter_token, sync_granularity, sync_direction, enable=true)
+ @repo_name = repo_name
+ @internal_repo_address = internal_repo_address
+ @inter_token = inter_token
+ @external_repo_address = external_repo_address
+ @exter_token = exter_token
+ @sync_granularity = sync_granularity
+ @sync_direction = sync_direction
+ @enable = enable
+ end
+
+ def call
+ result = post(url, request_params)
+ response = render_response(result)
+ end
+
+ private
+ def request_params
+ Hash.new.merge(data: {
+ repo_name: repo_name,
+ enable: enable,
+ internal_repo_address: internal_repo_address,
+ inter_token: inter_token,
+ external_repo_address: external_repo_address,
+ exter_token: exter_token,
+ sync_granularity: sync_granularity,
+ sync_direction: sync_direction
+ }.stringify_keys)
+ end
+
+ def url
+ "/cerobot/sync/repo".freeze
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/delete_branch_service.rb b/app/services/reposync/delete_branch_service.rb
new file mode 100644
index 000000000..6d3d7c328
--- /dev/null
+++ b/app/services/reposync/delete_branch_service.rb
@@ -0,0 +1,19 @@
+class Reposync::DeleteBranchService < Reposync::ClientService
+
+ attr_accessor :repo_name, :branch_name
+
+ def initialize(repo_name, branch_name)
+ @repo_name = repo_name
+ @branch_name = branch_name
+ end
+
+ def call
+ result = delete(url)
+ response = render_response(result)
+ end
+
+ private
+ def url
+ "/cerobot/sync/#{repo_name}/branch/#{branch_name}"
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/delete_repo_service.rb b/app/services/reposync/delete_repo_service.rb
new file mode 100644
index 000000000..420a2d60f
--- /dev/null
+++ b/app/services/reposync/delete_repo_service.rb
@@ -0,0 +1,18 @@
+class Reposync::DeleteRepoService < Reposync::ClientService
+
+ attr_accessor :repo_name
+
+ def initialize(repo_name)
+ @repo_name = repo_name
+ end
+
+ def call
+ result = delete(url)
+ response = render_response(result)
+ end
+
+ private
+ def url
+ "/cerobot/sync/repo/#{repo_name}"
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/get_logs_service.rb b/app/services/reposync/get_logs_service.rb
new file mode 100644
index 000000000..1288de6bf
--- /dev/null
+++ b/app/services/reposync/get_logs_service.rb
@@ -0,0 +1,33 @@
+class Reposync::GetLogsService < Reposync::ClientService
+
+ attr_accessor :repo_name, :branch_id, :page_num, :page_size
+
+ def initialize(repo_name=nil, branch_id=nil, page_num=1, page_size=10)
+ @repo_name = repo_name
+ @branch_id = branch_id
+ @page_num = page_num
+ @page_size = page_size
+ end
+
+ def call
+ result = get(url, request_params)
+ response = render_list_response(result)
+ end
+
+ private
+ def request_params
+ params = {
+ page_num: page_num,
+ page_size: page_size,
+ create_sort: true
+ }
+ params.merge!(repo_name: repo_name) if repo_name.present?
+ params.merge!(branch_id: branch_id) if branch_id.present?
+
+ return params.stringify_keys
+ end
+
+ def url
+ "/cerobot/sync/repo/logs"
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/get_sync_branches_service.rb b/app/services/reposync/get_sync_branches_service.rb
new file mode 100644
index 000000000..92c9d8565
--- /dev/null
+++ b/app/services/reposync/get_sync_branches_service.rb
@@ -0,0 +1,30 @@
+class Reposync::GetSyncBranchesService < Reposync::ClientService
+
+ attr_accessor :repo_name, :page, :limit, :create_sort
+
+ def initialize(repo_name, page=1, limit=10, create_sort=false)
+ @repo_name = repo_name
+ @page = page
+ @limit = limit
+ @create_sort = create_sort
+ end
+
+ def call
+ result = get(url, request_params)
+ response = render_response(result)
+ end
+
+ private
+ def request_params
+ {
+ page: page,
+ limit: limit,
+ create_sort: create_sort
+ }.stringify_keys
+ end
+
+ def url
+ "/cerobot/sync/#{repo_name}/branch".freeze
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/reposync/get_sync_repos_service.rb b/app/services/reposync/get_sync_repos_service.rb
new file mode 100644
index 000000000..3fc39d89f
--- /dev/null
+++ b/app/services/reposync/get_sync_repos_service.rb
@@ -0,0 +1,28 @@
+class Reposync::GetSyncReposService < Reposync::ClientService
+ attr_accessor :page, :limit, :create_sort
+
+ def initialize(page=1, limit=10, create_sort=false)
+ @page = page
+ @limit = limit
+ @create_sort = create_sort
+ end
+
+ def call
+ result = get(url, request_params)
+ response = render_response(result)
+ end
+
+ private
+ def request_params
+ {
+ page: page,
+ limit: limit,
+ create_sort: create_sort
+ }.stringify_keys
+ end
+
+ def url
+ "/cerobot/sync/repo".freeze
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/reposync/sync_branch_service.rb b/app/services/reposync/sync_branch_service.rb
new file mode 100644
index 000000000..9ca71b3fe
--- /dev/null
+++ b/app/services/reposync/sync_branch_service.rb
@@ -0,0 +1,22 @@
+class Reposync::SyncBranchService < Reposync::ClientService
+
+ attr_accessor :repo_name, :branch_name, :sync_direct
+
+ def initialize(repo_name, branch_name, sync_direct)
+ @repo_name = repo_name
+ @branch_name = branch_name
+ @sync_direct = sync_direct
+ end
+
+ def call
+ result = post(url)
+ response = render_response(result)
+ end
+
+ private
+
+ def url
+ "/cerobot/sync/#{repo_name}/branch/#{branch_name}?sync_direct=#{sync_direct}"
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/reposync/sync_repo_service.rb b/app/services/reposync/sync_repo_service.rb
new file mode 100644
index 000000000..8b9e6c247
--- /dev/null
+++ b/app/services/reposync/sync_repo_service.rb
@@ -0,0 +1,18 @@
+class Reposync::SyncRepoService < Reposync::ClientService
+
+ attr_accessor :repo_name
+
+ def initialize(repo_name)
+ @repo_name = repo_name
+ end
+
+ def call
+ result = post(url)
+ response = render_response(result)
+ end
+
+ private
+ def url
+ "/cerobot/sync/repo/#{repo_name}"
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/update_branch_status_service.rb b/app/services/reposync/update_branch_status_service.rb
new file mode 100644
index 000000000..98ebcd5b5
--- /dev/null
+++ b/app/services/reposync/update_branch_status_service.rb
@@ -0,0 +1,21 @@
+class Reposync::UpdateBranchStatusService < Reposync::ClientService
+
+ attr_accessor :repo_name, :branch_name, :enable
+
+ def initialize(repo_name, branch_name, enable)
+ @repo_name = repo_name
+ @branch_name = branch_name
+ @enable = enable
+ end
+
+ def call
+ result = put(url)
+ response = render_response(result)
+ end
+
+ private
+
+ def url
+ "/cerobot/sync/#{repo_name}/branch/#{branch_name}?enable=#{enable}"
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/update_repo_addr_service.rb b/app/services/reposync/update_repo_addr_service.rb
new file mode 100644
index 000000000..aa34f4fb3
--- /dev/null
+++ b/app/services/reposync/update_repo_addr_service.rb
@@ -0,0 +1,31 @@
+class Reposync::UpdateRepoAddrService < Reposync::ClientService
+
+ attr_accessor :repo_name, :internal_repo_address, :inter_token, :external_repo_address, :exter_token
+
+ def initialize(repo_name, internal_repo_address, inter_token, external_repo_address, exter_token)
+ @repo_name = repo_name
+ @internal_repo_address = internal_repo_address
+ @inter_token = inter_token
+ @external_repo_address = external_repo_address
+ @exter_token = exter_token
+ end
+
+ def call
+ result = put(url, request_params)
+ response = render_response(result)
+ end
+
+ private
+ def request_params
+ Hash.new.merge(data: {
+ internal_repo_address: internal_repo_address,
+ inter_token: inter_token,
+ external_repo_address: external_repo_address,
+ exter_token: exter_token
+ }.stringify_keys)
+ end
+
+ def url
+ "/cerobot/sync/repo/#{repo_name}/repo_addr".freeze
+ end
+end
\ No newline at end of file
diff --git a/app/services/reposync/update_repo_status_service.rb b/app/services/reposync/update_repo_status_service.rb
new file mode 100644
index 000000000..db7065d85
--- /dev/null
+++ b/app/services/reposync/update_repo_status_service.rb
@@ -0,0 +1,19 @@
+class Reposync::UpdateRepoStatusService < Reposync::ClientService
+
+ attr_accessor :repo_name, :enable
+
+ def initialize(repo_name, enable)
+ @repo_name = repo_name
+ @enable = enable
+ end
+
+ def call
+ result = put(url)
+ response = render_response(result)
+ end
+
+ private
+ def url
+ "/cerobot/sync/repo/#{repo_name}?enable=#{enable}".freeze
+ end
+end
\ No newline at end of file
diff --git a/app/views/action/node_inputs/_form.html.erb b/app/views/action/node_inputs/_form.html.erb
new file mode 100644
index 000000000..deccfa69e
--- /dev/null
+++ b/app/views/action/node_inputs/_form.html.erb
@@ -0,0 +1,39 @@
+<%= form_with(model: node_input, url: action_node_node_inputs_path(@node), local: true) do |form| %>
+ <% if node_input.errors.any? %>
+
+
<%= pluralize(node_input.errors.count, "error") %> prohibited this node_input from being saved:
+
+
+ <% node_input.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, "参数名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :input_type, "参数类型" %>
+ <%= form.text_field :input_type %>
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+ <%= form.label :is_required, "是否必填项" %>
+ <%= form.check_box("is_required", {}, "true", "false") %>
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+
+
+ <%= form.submit("保存") %>
+
+<% end %>
diff --git a/app/views/action/node_inputs/_node_input.json.jbuilder b/app/views/action/node_inputs/_node_input.json.jbuilder
new file mode 100644
index 000000000..9f0a6074b
--- /dev/null
+++ b/app/views/action/node_inputs/_node_input.json.jbuilder
@@ -0,0 +1,6 @@
+json.extract! node_input, :id, :name, :input_type, :description
+if node_input.input_type.to_s == "select"
+ json.select node.action_node_selects do |node_select|
+ json.partial! "node_select", locals: { node_select: node_select, node: node }
+ end
+end
diff --git a/app/views/action/node_inputs/_node_select.json.jbuilder b/app/views/action/node_inputs/_node_select.json.jbuilder
new file mode 100644
index 000000000..4e90508e9
--- /dev/null
+++ b/app/views/action/node_inputs/_node_select.json.jbuilder
@@ -0,0 +1,4 @@
+json.extract! node_select, :id, :version
+if node.is_local?
+ json.local_url node.local_url
+end
diff --git a/app/views/action/node_inputs/edit.html.erb b/app/views/action/node_inputs/edit.html.erb
new file mode 100644
index 000000000..6ae9f8a78
--- /dev/null
+++ b/app/views/action/node_inputs/edit.html.erb
@@ -0,0 +1,47 @@
+编辑
+
+
+
+<%= form_with(model: @node_input, url: action_node_node_input_path(@node,@node_input), local: true) do |form| %>
+ <% if @node_input.errors.any? %>
+
+
<%= pluralize(@node_input.errors.count, "error") %> prohibited this node_input from being saved:
+
+
+ <% @node_input.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, "参数名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :input_type, "参数类型" %>
+ <%= form.text_field :input_type %>
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+ <%= form.label :is_required, "是否必填项" %>
+ <%= form.check_box("is_required", {}, "true", "false") %>
+
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+
+
+ <%= form.submit("保存赛事") %>
+
+<% end %>
+
+
+<%= link_to 'Back', action_node_node_inputs_path(@node) %>
diff --git a/app/views/action/node_inputs/index.html.erb b/app/views/action/node_inputs/index.html.erb
new file mode 100644
index 000000000..f070b3dd7
--- /dev/null
+++ b/app/views/action/node_inputs/index.html.erb
@@ -0,0 +1,46 @@
+<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+action 节点参数配置<%= link_to '>>>Back action节点首页', action_nodes_path %>
+
+
+说明:该界面适用于action 节点参数配置
+
+
+
+
+ ID
+ 参数名称
+ 参数输入类型
+ 参数描述
+ 是否必填项
+ 排序号
+ 更新时间
+ 操作
+
+
+
+
+ <% @node_inputs.each do |info| %>
+
+ <%= info.id %>
+ <%= info.name %>
+ <%= info.input_type %>
+ <% if info.input_type == "select" %>
+ <%= select_tag(:version, options_for_select(@node.action_node_selects.map(&:value)), class: 'form-control') %>
+ <%= link_to '修改选择项', edit_action_node_node_input_path(@node.id, info) %>
+ <% end %>
+
+ <%= info.description %>
+ <%= info.is_required %>
+ <%= info.sort_no %>
+ <%= info.updated_at&.strftime('%Y-%m-%d %H:%M') %>
+ <%= link_to '编辑', edit_action_node_node_input_path(@node.id, info) %>
+ <%= link_to 'Destroy', action_node_node_input_path(@node, info), method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %>
+
+
+
+
+
+<%= link_to '新增', new_action_node_node_input_path(@node) %>
diff --git a/app/views/action/node_inputs/index.json.jbuilder b/app/views/action/node_inputs/index.json.jbuilder
new file mode 100644
index 000000000..3909639ce
--- /dev/null
+++ b/app/views/action/node_inputs/index.json.jbuilder
@@ -0,0 +1,20 @@
+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.inputs node.action_node_inputs do |node_input|
+ json.partial! "node_input", locals: { node_input: node_input, node: node }
+ end
+ end
+ 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.inputs node.action_node_inputs do |node_input|
+ json.partial! "node_input", locals: { node_input: node_input, node: node }
+ end
+ end
+ end
+
+end
diff --git a/app/views/action/node_inputs/new.html.erb b/app/views/action/node_inputs/new.html.erb
new file mode 100644
index 000000000..965ae9378
--- /dev/null
+++ b/app/views/action/node_inputs/new.html.erb
@@ -0,0 +1,5 @@
+新增
+
+<%= render 'form', node_input: @node_input %>
+
+<%= link_to 'Back', action_node_node_inputs_path(@node) %>
\ No newline at end of file
diff --git a/app/views/action/node_inputs/show.json.jbuilder b/app/views/action/node_inputs/show.json.jbuilder
new file mode 100644
index 000000000..64548544d
--- /dev/null
+++ b/app/views/action/node_inputs/show.json.jbuilder
@@ -0,0 +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.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/action/node_selects/_form.html.erb b/app/views/action/node_selects/_form.html.erb
new file mode 100644
index 000000000..7dd4a6637
--- /dev/null
+++ b/app/views/action/node_selects/_form.html.erb
@@ -0,0 +1,39 @@
+<%= form_with(model: node_select, url: action_node_node_selects_path(@node), local: true) do |form| %>
+ <% if node_select.errors.any? %>
+
+
<%= pluralize(node_select.errors.count, "error") %> prohibited this node select from being saved:
+
+
+ <% node_select.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, "选择项名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :val, "选择项值" %>
+ <%= form.text_field :val %>
+
+
+ <%= form.label :val_ext, "选择项值扩展" %>
+ <%= form.text_field :val_ext %>
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+
+
+ <%= form.submit("保存") %>
+
+<% end %>
diff --git a/app/views/action/node_selects/edit.html.erb b/app/views/action/node_selects/edit.html.erb
new file mode 100644
index 000000000..fca5b3ce7
--- /dev/null
+++ b/app/views/action/node_selects/edit.html.erb
@@ -0,0 +1,46 @@
+编辑
+
+
+
+<%= form_with(model: @node_select, url: action_node_node_select_path(@node,@node_select), local: true) do |form| %>
+ <% if @node_select.errors.any? %>
+
+
<%= pluralize(@node_select.errors.count, "error") %> prohibited this node select from being saved:
+
+
+ <% @node_select.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, "选择项名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :val, "选择项值" %>
+ <%= form.text_field :val %>
+
+
+ <%= form.label :val_ext, "选择项值扩展" %>
+ <%= form.text_field :val_ext %>
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+
+
+ <%= form.submit("保存") %>
+
+<% end %>
+
+
+<%= link_to 'Back', action_node_node_inputs_path(@node) %>
diff --git a/app/views/action/node_selects/index.html.erb b/app/views/action/node_selects/index.html.erb
new file mode 100644
index 000000000..09d427d97
--- /dev/null
+++ b/app/views/action/node_selects/index.html.erb
@@ -0,0 +1,43 @@
+<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+action 节点参数配置<%= link_to '>>>Back action节点首页', action_nodes_path %>
+
+
+说明:该界面适用于action 节点参数配置
+
+
+
+
+ ID
+ 选择项名称
+ 选择项值
+ 选择项值扩展
+ 描述
+ 下载地址
+ 排序号
+ 更新时间
+ 操作
+
+
+
+
+ <% @node_selects.each do |info| %>
+
+ <%= info.id %>
+ <%= info.name %>
+ <%= info.val %>
+ <%= info.val_ext %>
+ <%= info.description %>
+ <%= info.download_url %>
+ <%= info.sort_no %>
+ <%= info.updated_at&.strftime('%Y-%m-%d %H:%M') %>
+ <%= link_to '编辑', edit_action_node_node_select_path(@node.id, info) %>
+ <%= link_to 'Destroy', action_node_node_select_path(@node, info), method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %>
+
+
+
+
+
+<%= link_to '新增', new_action_node_node_input_path(@node) %>
diff --git a/app/views/action/node_selects/new.html.erb b/app/views/action/node_selects/new.html.erb
new file mode 100644
index 000000000..c2fcaf123
--- /dev/null
+++ b/app/views/action/node_selects/new.html.erb
@@ -0,0 +1,5 @@
+新增
+
+<%= render 'form', node_select: @node_select %>
+
+<%= link_to 'Back', action_node_node_selects_path(@node) %>
\ No newline at end of file
diff --git a/app/views/action/node_types/_form.html.erb b/app/views/action/node_types/_form.html.erb
new file mode 100644
index 000000000..9a6a5d693
--- /dev/null
+++ b/app/views/action/node_types/_form.html.erb
@@ -0,0 +1,31 @@
+<%= form_with(model: node_type, local: true) do |form| %>
+ <% if node_type.errors.any? %>
+
+
<%= pluralize(node_type.errors.count, "error") %> prohibited this node type from being saved:
+
+
+ <% node_type.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, "分类名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+
+
+ <%= form.submit("保存") %>
+
+<% end %>
diff --git a/app/views/action/node_types/edit.html.erb b/app/views/action/node_types/edit.html.erb
new file mode 100644
index 000000000..9a44930a4
--- /dev/null
+++ b/app/views/action/node_types/edit.html.erb
@@ -0,0 +1,7 @@
+编辑
+
+
+<%= render 'form', node_type: @node_type %>
+
+
+<%= link_to 'Back', action_node_types_path %>
diff --git a/app/views/action/node_types/index.html.erb b/app/views/action/node_types/index.html.erb
new file mode 100644
index 000000000..812d06a24
--- /dev/null
+++ b/app/views/action/node_types/index.html.erb
@@ -0,0 +1,37 @@
+<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+action 节点分类配置<%= link_to '>>>Back action节点首页', action_nodes_path %>
+
+
+说明:该界面适用于action 节点分类配置
+
+
+
+
+ ID
+ 分类名称
+ 描述
+ 排序号
+ 更新时间
+ 操作
+
+
+
+
+ <% @node_types.each do |info| %>
+
+ <%= info.id %>
+ <%= info.name %>
+ <%= info.description %>
+ <%= info.sort_no %>
+ <%= info.updated_at&.strftime('%Y-%m-%d %H:%M') %>
+ <%= link_to '编辑', edit_action_node_type_path(info) %>
+ <%= link_to 'Destroy', action_node_type_path(info), method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %>
+
+
+
+
+
+<%= link_to '新增', new_action_node_type_path %>
diff --git a/app/views/action/node_types/new.html.erb b/app/views/action/node_types/new.html.erb
new file mode 100644
index 000000000..1e053b9da
--- /dev/null
+++ b/app/views/action/node_types/new.html.erb
@@ -0,0 +1,5 @@
+新增
+
+<%= render 'form', node_type: @node_type %>
+
+<%= link_to 'Back', action_node_types_path %>
\ No newline at end of file
diff --git a/app/views/action/nodes/_form.html.erb b/app/views/action/nodes/_form.html.erb
new file mode 100644
index 000000000..4e40f0ff0
--- /dev/null
+++ b/app/views/action/nodes/_form.html.erb
@@ -0,0 +1,63 @@
+<%= form_with(model: node, local: true) do |form| %>
+ <%# if node.errors.any? %>
+
+
+
+
+ <%# node.errors.full_messages.each do |message| %>
+
+ <%# end %>
+
+
+ <%# end %>
+
+ 类型:
+ <%= form.select :action_node_types_id, options_for_select(Action::NodeType.all.map { |key| [key.name, key.id]}, node.action_node_types_id), {}, class: "form-control" %>
+
+
+
+ <%= form.label :name, "节点名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :full_name, "节点全称" %>
+ <%= form.text_field :full_name %>
+
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+
+ <%= form.label :icon, "Icon图标" %>
+ <%= form.text_field :icon %>
+
+
+
+ <%= form.label :is_local, "是否本地化" %>
+ <%= form.check_box("is_local", {}, "true", "false") %>
+
+
+
+ <%= form.label :local_url, "本地化地址" %>
+ <%= form.text_field :local_url, :style => 'width:1200px;' %>
+
+
+
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+
+ <%= form.label :yaml, "yaml语法代码" %>
+ <%= form.text_area :yaml, rows: 5, :style => 'width:1200px;' %>
+
+
+
+
+ <%= form.submit("保存") %>
+
+<% end %>
diff --git a/app/views/action/nodes/_node_input.json.jbuilder b/app/views/action/nodes/_node_input.json.jbuilder
new file mode 100644
index 000000000..c41f93c74
--- /dev/null
+++ b/app/views/action/nodes/_node_input.json.jbuilder
@@ -0,0 +1,6 @@
+json.extract! node_input, :id, :name, :input_type, :description, :is_required
+if node_input.input_type.to_s == "select"
+ json.select node.action_node_selects do |node_select|
+ json.partial! "node_select", locals: { node_select: node_select, node: node }
+ end
+end
diff --git a/app/views/action/nodes/_node_select.json.jbuilder b/app/views/action/nodes/_node_select.json.jbuilder
new file mode 100644
index 000000000..897e3f8ef
--- /dev/null
+++ b/app/views/action/nodes/_node_select.json.jbuilder
@@ -0,0 +1,4 @@
+json.extract! node_select, :id, :val
+if node.is_local?
+ json.local_url node.local_url
+end
diff --git a/app/views/action/nodes/edit.html.erb b/app/views/action/nodes/edit.html.erb
new file mode 100644
index 000000000..ee9cb0947
--- /dev/null
+++ b/app/views/action/nodes/edit.html.erb
@@ -0,0 +1,7 @@
+编辑
+
+
+
+<%= render 'form', node: @node %>
+
+<%= link_to 'Back', action_nodes_path %>
diff --git a/app/views/action/nodes/index.html.erb b/app/views/action/nodes/index.html.erb
new file mode 100644
index 000000000..763a8e467
--- /dev/null
+++ b/app/views/action/nodes/index.html.erb
@@ -0,0 +1,49 @@
+<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+action 节点配置
+>>前往节点分类配置
+>>前往模板配置
+说明:该界面适用于action 节点配置参数配置
+
+
+
+
+ ID
+ 节点名称
+ 节点全称
+ 节点描述
+ 分类
+
+
+
+
+ 排序号
+ 更新时间
+ 操作
+
+
+
+
+ <% @nodes.each do |info| %>
+
+ <%= info.id %>
+ <%= info.name %>
+ <%= info.full_name %>
+ <%= info.description %>
+ <%= info.action_node_type&.name %>
+
+
+
+ <%= info.sort_no %>
+ <%= info.updated_at&.strftime('%Y-%m-%d %H:%M') %>
+ <%= link_to '编辑', edit_action_node_path(info) %>
+ <%= link_to '参数列表', action_node_node_inputs_path(info) %>
+ <%= link_to 'Destroy', info, method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %>
+
+
+
+
+
+<%= link_to '新增', new_action_node_path %>
diff --git a/app/views/action/nodes/index.json.jbuilder b/app/views/action/nodes/index.json.jbuilder
new file mode 100644
index 000000000..b81879e20
--- /dev/null
+++ b/app/views/action/nodes/index.json.jbuilder
@@ -0,0 +1,20 @@
+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, :icon, :action_node_types_id, :yaml, :sort_no, :use_count
+ json.inputs node.action_node_inputs do |node_input|
+ json.partial! "node_input", locals: { node_input: node_input, node: node }
+ end
+ end
+ else
+ json.extract! node_type, :id, :name
+ json.nodes node_type.action_nodes do |node|
+ json.extract! node, :id, :name, :full_name, :description, :icon, :action_node_types_id, :yaml, :sort_no, :use_count
+ json.inputs node.action_node_inputs do |node_input|
+ json.partial! "node_input", locals: { node_input: node_input, node: node }
+ end
+ end
+ end
+
+end
diff --git a/app/views/action/nodes/new.html.erb b/app/views/action/nodes/new.html.erb
new file mode 100644
index 000000000..4a5d2fae9
--- /dev/null
+++ b/app/views/action/nodes/new.html.erb
@@ -0,0 +1,5 @@
+新增
+
+<%= render 'form', node: @node %>
+
+<%= link_to 'Back', action_nodes_path %>
\ No newline at end of file
diff --git a/app/views/action/nodes/show.json.jbuilder b/app/views/action/nodes/show.json.jbuilder
new file mode 100644
index 000000000..64548544d
--- /dev/null
+++ b/app/views/action/nodes/show.json.jbuilder
@@ -0,0 +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.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/action/templates/_form.html.erb b/app/views/action/templates/_form.html.erb
new file mode 100644
index 000000000..7d93cb76d
--- /dev/null
+++ b/app/views/action/templates/_form.html.erb
@@ -0,0 +1,43 @@
+<%= form_with(model: template, local: true) do |form| %>
+ <% if template.errors.any? %>
+
+
<%= pluralize(template.errors.count, "error") %> prohibited this node type from being saved:
+
+
+ <% template.errors.full_messages.each do |message| %>
+ <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, "模板名称" %>
+ <%= form.text_field :name %>
+
+
+ <%= form.label :description, "描述" %>
+ <%= form.text_area :description, rows: 5, :style => 'width:800px;' %>
+
+
+ <%= form.label :img, "配图" %>
+ <%= form.text_field :img %>
+
+
+ <%= form.label :sort_no, "排序号" %>
+ <%= form.text_field :sort_no %>
+
+
+ <%= form.label :yaml, "yaml语法代码" %>
+ <%= form.text_area :yaml, rows: 5, :style => 'width:1200px;' %>
+
+
+ <%= form.label :json, "json语法代码" %>
+ <%= form.text_area :json, rows: 5, :style => 'width:1200px;' %>
+
+
+
+
+ <%= form.submit("保存") %>
+
+<% end %>
diff --git a/app/views/action/templates/edit.html.erb b/app/views/action/templates/edit.html.erb
new file mode 100644
index 000000000..f9c1effc1
--- /dev/null
+++ b/app/views/action/templates/edit.html.erb
@@ -0,0 +1,6 @@
+编辑
+
+
+<%= render 'form', template: @template %>
+
+<%= link_to 'Back', action_templates_path %>
diff --git a/app/views/action/templates/index.html.erb b/app/views/action/templates/index.html.erb
new file mode 100644
index 000000000..b71aaede9
--- /dev/null
+++ b/app/views/action/templates/index.html.erb
@@ -0,0 +1,37 @@
+<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+action 模板配置<%= link_to '>>>Back action节点首页', action_nodes_path %>
+
+
+说明:该界面适用于action 模板配置
+
+
+
+
+ ID
+ 模板名称
+ 描述
+ 排序号
+ 更新时间
+ 操作
+
+
+
+
+ <% @templates.each do |info| %>
+
+ <%= info.id %>
+ <%= info.name %>
+ <%= info.description %>
+ <%= info.sort_no %>
+ <%= info.updated_at&.strftime('%Y-%m-%d %H:%M') %>
+ <%= link_to '编辑', edit_action_template_path(info) %>
+ <%= link_to 'Destroy', action_template_path(info), method: :delete, data: { confirm: 'Are you sure?' } %>
+
+ <% end %>
+
+
+
+
+
+<%= link_to '新增', new_action_template_path %>
diff --git a/app/views/action/templates/index.json.jbuilder b/app/views/action/templates/index.json.jbuilder
new file mode 100644
index 000000000..da534c842
--- /dev/null
+++ b/app/views/action/templates/index.json.jbuilder
@@ -0,0 +1,3 @@
+json.templates @templates.each do |tpl|
+ json.extract! tpl, :id, :name, :description, :img, :sort_no, :json, :yaml
+end
diff --git a/app/views/action/templates/new.html.erb b/app/views/action/templates/new.html.erb
new file mode 100644
index 000000000..e67c6a674
--- /dev/null
+++ b/app/views/action/templates/new.html.erb
@@ -0,0 +1,5 @@
+新增
+
+<%= render 'form', template: @template %>
+
+<%= link_to 'Back', action_templates_path %>
\ No newline at end of file
diff --git a/app/views/action/templates/show.json.jbuilder b/app/views/action/templates/show.json.jbuilder
new file mode 100644
index 000000000..5db67424b
--- /dev/null
+++ b/app/views/action/templates/show.json.jbuilder
@@ -0,0 +1 @@
+json.extract! @template, :id, :name, :description, :img, :sort_no, :json, :yaml
\ No newline at end of file
diff --git a/app/views/admins/dashboards/_baidu_tongji.html.erb b/app/views/admins/dashboards/_baidu_tongji.html.erb
new file mode 100644
index 000000000..75fd1cde3
--- /dev/null
+++ b/app/views/admins/dashboards/_baidu_tongji.html.erb
@@ -0,0 +1,68 @@
+
+ 数据来源百度统计,本周 [<%= @current_week_statistic.first&.date %> / <%= @current_week_statistic.last&.date %>]
+
+
+
+
+ 日期
+ 访问量
+ 访客数
+ IP数
+ 直接访问占比
+ 外部链接占比
+ 搜索引擎占比
+ 自定义
+
+
+
+ <% if @current_week_statistic.size ==1 && @pre_week_statistic.present? %>
+
+ 上周合计
+ <%= @pre_week_statistic.map(&:pv).sum %>
+ <%= @pre_week_statistic.map(&:visitor).sum %>
+ <%= @pre_week_statistic.map(&:ip).sum %>
+ <%= (@pre_week_statistic.map(&:source_through).sum.to_f / 7).round(2) %>%
+ <%= (@pre_week_statistic.map(&:source_link).sum.to_f / 7).round(2) %>%
+ <%= (@pre_week_statistic.map(&:source_search).sum.to_f / 7).round(2) %>%
+ <%= ((@pre_week_statistic.map(&:source_custom) - [nil]).sum.to_f / 7).round(2) %>%
+
+
+ <% end %>
+ <% @current_week_statistic.each_with_index do |week, index| %>
+
+ <%= week.date %>
+ <%= week.pv %>
+ <%= week.visitor %>
+ <%= week.ip %>
+ <%= week.source_through %>%
+ <%= week.source_link %>%
+ <%= week.source_search %>%
+ <%= week.source_custom.to_f %>%
+
+
+ <% end %>
+
+ <% current_week_size = @current_week_statistic.size %>
+ 本周合计
+ <%= @current_week_statistic.map(&:pv).sum %>
+ <%= @current_week_statistic.map(&:visitor).sum %>
+ <%= @current_week_statistic.map(&:ip).sum %>
+ <%= (@current_week_statistic.map(&:source_through).sum.to_f / current_week_size).round(2) %>%
+ <%= (@current_week_statistic.map(&:source_link).sum.to_f / current_week_size).round(2) %>%
+ <%= (@current_week_statistic.map(&:source_search).sum.to_f / current_week_size).round(2) %>%
+ <%= ((@current_week_statistic.map(&:source_custom) - [nil]).sum.to_f / current_week_size).round(2) %>%
+
+
+
+
+
+
+<% unless @access_token.present? && @overview_data.present? %>
+
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/admins/dashboards/_baidu_tongji_api.html.erb b/app/views/admins/dashboards/_baidu_tongji_api.html.erb
new file mode 100644
index 000000000..3a18dc405
--- /dev/null
+++ b/app/views/admins/dashboards/_baidu_tongji_api.html.erb
@@ -0,0 +1,67 @@
+<% if @access_token.present? && @overview_data.present? %>
+
+ 数据来源百度统计,本周 <%= @overview_data['timeSpan'] %>
+
+
+
+
+ 日期
+ 访问量
+ 访客数
+ IP数
+
+
+
+ <% pv_count = [] %>
+ <% visitor_count = [] %>
+ <% ip_count = [] %>
+ <% @overview_data['items'][0].each_with_index do |date, index| %>
+ <% pv = @overview_data['items'][1][index][0] %>
+ <% visitor = @overview_data['items'][1][index][1] %>
+ <% ip = @overview_data['items'][1][index][2] %>
+
+ <%= date[0] %>
+ <%= pv %>
+ <%= visitor %>
+ <%= ip %>
+
+
+ <% pv_count.push(pv) %>
+ <% visitor_count.push(visitor) %>
+ <% ip_count.push(ip) %>
+ <% end %>
+
+ 合计
+ <%= pv_count %>
+ <%= visitor_count %>
+ <%= ip_count %>
+
+
+
+
+
+
+
+ 直接访问占比
+ 外部链接占比
+ 搜索引擎占比
+ 自定义
+
+
+
+
+ <% @source_from_data['items'][1].each_with_index do |source, index| %>
+ <%= ((source[0].to_f / @source_from_data['sum'][0][0].to_f) * 100).round(2).to_s %>%
+ <% end %>
+
+
+
+<% else %>
+
+
+<% 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 2d86b4b6c..a55ef55e4 100644
--- a/app/views/admins/dashboards/index.html.erb
+++ b/app/views/admins/dashboards/index.html.erb
@@ -2,6 +2,38 @@
<% add_admin_breadcrumb('概览', admins_path) %>
<% end %>
+<% cache "/admin/dashboards/#{Time.now.strftime('%Y-%m-%d')}", :expires_in => 1.days do %>
+
+<%end %>
+
+ <% if EduSetting.get("open_baidu_tongji").to_s == "true" %>
+ <%= render partial: 'admins/dashboards/baidu_tongji' %>
+ <% end %>
\ No newline at end of file
diff --git a/app/views/admins/identity_verifications/edit.html.erb b/app/views/admins/identity_verifications/edit.html.erb
index 580c86f3f..28289e74f 100644
--- a/app/views/admins/identity_verifications/edit.html.erb
+++ b/app/views/admins/identity_verifications/edit.html.erb
@@ -106,7 +106,7 @@
- <%= f.input :description, as: :text,label: '拒绝理由:(拒绝时请填写拒绝理由,可以为空)', wrapper_html: { class: 'col-md-12' }, input_html: { maxlength: 100, size: 40, class: 'col-md-11' , value: @identity_verification.description } %>
+ <%= f.input :description, as: :text,label: '拒绝理由:(拒绝时请填写拒绝理由,不可以为空)', wrapper_html: { class: 'col-md-12' }, input_html: { maxlength: 100, size: 40, class: 'col-md-11' , value: @identity_verification.description } %>
<%= f.button :submit, value: '保存', class: 'btn-primary mr-3 px-4' %>
diff --git a/app/views/admins/issues_rank/index.html.erb b/app/views/admins/issues_rank/index.html.erb
new file mode 100644
index 000000000..45ef1e6d2
--- /dev/null
+++ b/app/views/admins/issues_rank/index.html.erb
@@ -0,0 +1,37 @@
+<% define_admin_breadcrumbs do %>
+ <% add_admin_breadcrumb('疑修排行榜', admins_path) %>
+<% end %>
+
+
+
+
+
+ <%= render partial: 'admins/issues_rank/shared/data_list', locals: { statistics: @statistics } %>
+
+
\ No newline at end of file
diff --git a/app/views/admins/issues_rank/index.js.erb b/app/views/admins/issues_rank/index.js.erb
new file mode 100644
index 000000000..189206df0
--- /dev/null
+++ b/app/views/admins/issues_rank/index.js.erb
@@ -0,0 +1 @@
+$('.issue-rank-list-container').html("<%= j( render partial: 'admins/issues_rank/shared/data_list', locals: { statistics: @statistics } ) %>");
\ No newline at end of file
diff --git a/app/views/admins/issues_rank/shared/_data_list.html.erb b/app/views/admins/issues_rank/shared/_data_list.html.erb
new file mode 100644
index 000000000..9512028c3
--- /dev/null
+++ b/app/views/admins/issues_rank/shared/_data_list.html.erb
@@ -0,0 +1,26 @@
+
+
+
+ 排名
+ 项目
+ 新增疑修数
+ 关闭疑修数
+ 当前疑修数量
+
+
+
+ <% statistics.each_with_index do |item, index| %>
+
+ <%= index + 1%>
+
+ ">
+ <%= "#{item&.project&.owner&.real_name}/#{item&.project&.name}" %>
+
+
+ <%= item&.issues %>
+ <%= item&.closed_issues %>
+ <%= item&.project&.issues&.issue_issue.count %>
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/admins/page_themes/_form_modal.html.erb b/app/views/admins/page_themes/_form_modal.html.erb
index 3d0a97588..5a89bf2bd 100644
--- a/app/views/admins/page_themes/_form_modal.html.erb
+++ b/app/views/admins/page_themes/_form_modal.html.erb
@@ -14,7 +14,7 @@
建站工具 *
- <% state_options = [['hugo', "hugo"], ['jeklly', "jeklly"],['hexo',"hexo"]] %>
+ <% state_options = [['hugo', "hugo"], ['jeklly', "jeklly"],['hexo',"hexo"],['files',"files"]] %>
<%= select_tag('page_theme[language_frame]', options_for_select(state_options), class: 'form-control') %>
<% end%>
diff --git a/app/views/admins/page_themes/index.html.erb b/app/views/admins/page_themes/index.html.erb
index 4b4392d88..91dd0e34e 100644
--- a/app/views/admins/page_themes/index.html.erb
+++ b/app/views/admins/page_themes/index.html.erb
@@ -6,7 +6,7 @@
<%= form_tag(admins_page_themes_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
建站工具:
- <% state_options = [['全部',nil], ['hugo', 0], ['jeklly', 1],['hexo',2]] %>
+ <% state_options = [['全部',nil], ['hugo', 0], ['jeklly', 1],['hexo',2],['files',3]] %>
<%= select_tag(:language_frame, options_for_select(state_options), class: 'form-control') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
diff --git a/app/views/admins/projects/index.html.erb b/app/views/admins/projects/index.html.erb
index af93598c9..35d931b02 100644
--- a/app/views/admins/projects/index.html.erb
+++ b/app/views/admins/projects/index.html.erb
@@ -4,7 +4,7 @@
<%= form_tag(admins_projects_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
- <%= text_field_tag(:search, params[:search], class: 'form-control col-12 col-md-2 mr-3', placeholder: '项目名称检索') %>
+ <%= text_field_tag(:search, params[:search], class: 'form-control col-12 col-md-2 mr-3', placeholder: '项目名称/标识检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<% end %>
diff --git a/app/views/admins/projects_rank/index.html.erb b/app/views/admins/projects_rank/index.html.erb
index e8f334f05..2e33a5335 100644
--- a/app/views/admins/projects_rank/index.html.erb
+++ b/app/views/admins/projects_rank/index.html.erb
@@ -7,11 +7,11 @@
<%= form_tag(admins_projects_rank_index_path, method: :get, class: 'form-inline search-form flex-1', id: 'project-rank-date-form') do %>
开始日期
-
+
截止日期
-
+
<% end %>
<%= link_to '导出', "/项目活跃度排行.xls", class: 'btn btn-primary mr-3' %>
diff --git a/app/views/admins/shared/_sidebar.html.erb b/app/views/admins/shared/_sidebar.html.erb
index 4db545074..f9bf9ae48 100644
--- a/app/views/admins/shared/_sidebar.html.erb
+++ b/app/views/admins/shared/_sidebar.html.erb
@@ -81,6 +81,7 @@
<%= sidebar_item_group('#rank-submenu', '活跃度排行', icon: 'calendar') do %>
<%= sidebar_item(admins_users_rank_index_path, '用户活跃度排行', icon: 'user', controller: 'admins-users_rank') %>
<%= sidebar_item(admins_projects_rank_index_path, '项目活跃度排行', icon: 'database', controller: 'admins-projects_rank') %>
+
<%= sidebar_item(admins_issues_rank_index_path, '疑修活跃度排行', icon: 'calendar', controller: 'admins-issues_rank') %>
<% end %>
diff --git a/app/views/api/pm/issue_links/index.json.jbuilder b/app/views/api/pm/issue_links/index.json.jbuilder
new file mode 100644
index 000000000..5a563feba
--- /dev/null
+++ b/app/views/api/pm/issue_links/index.json.jbuilder
@@ -0,0 +1,4 @@
+
+json.issues @links.each do |link|
+ json.partial! "api/v1/issues/simple_detail", locals: { issue: link.be_linkable }
+end
\ No newline at end of file
diff --git a/app/views/api/pm/issues/parent_issues.json.jbuilder b/app/views/api/pm/issues/parent_issues.json.jbuilder
new file mode 100644
index 000000000..a9a930d6a
--- /dev/null
+++ b/app/views/api/pm/issues/parent_issues.json.jbuilder
@@ -0,0 +1,8 @@
+json.total_count @issues.total_count
+json.issues @issues.each do |issue|
+ if params[:only_name].present?
+ json.(issue, :id, :subject, :project_issues_index)
+ else
+ json.partial! "api/v1/issues/simple_detail", locals: {issue: issue}
+ end
+end
\ No newline at end of file
diff --git a/app/views/api/v1/attachments/_detail.json.jbuilder b/app/views/api/v1/attachments/_detail.json.jbuilder
new file mode 100644
index 000000000..7998812f4
--- /dev/null
+++ b/app/views/api/v1/attachments/_detail.json.jbuilder
@@ -0,0 +1,11 @@
+json.id attachment.uuid
+json.title attachment.title
+json.description attachment.description
+json.filesize number_to_human_size(attachment.filesize)
+json.is_pdf attachment.is_pdf?
+json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)
+json.created_on attachment.created_on.strftime("%Y-%m-%d %H:%M:%S")
+json.content_type attachment.content_type
+json.creator do
+ json.partial! "api/v1/users/simple_user", locals: {user: attachment.author}
+end
\ No newline at end of file
diff --git a/app/views/api/v1/attachments/_simple_detail.json.jbuilder b/app/views/api/v1/attachments/_simple_detail.json.jbuilder
index 3d56eb82f..20ebe5687 100644
--- a/app/views/api/v1/attachments/_simple_detail.json.jbuilder
+++ b/app/views/api/v1/attachments/_simple_detail.json.jbuilder
@@ -1,7 +1,8 @@
-json.id attachment.id
+json.id attachment.uuid
json.title attachment.title
+json.description attachment.description
json.filesize number_to_human_size(attachment.filesize)
json.is_pdf attachment.is_pdf?
-json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)
+json.url Rails.application.config_for(:configuration)['platform_url'] + (attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)).to_s
json.created_on attachment.created_on.strftime("%Y-%m-%d %H:%M")
json.content_type attachment.content_type
diff --git a/app/views/api/v1/issues/_detail.json.jbuilder b/app/views/api/v1/issues/_detail.json.jbuilder
index 7c3adecc3..8b5ff5949 100644
--- a/app/views/api/v1/issues/_detail.json.jbuilder
+++ b/app/views/api/v1/issues/_detail.json.jbuilder
@@ -33,6 +33,13 @@ json.author do
json.nil!
end
end
+json.changer do
+ if issue.changer.present?
+ json.partial! "api/v1/users/simple_user", locals: {user: issue.changer}
+ else
+ json.nil!
+ end
+end
json.assigners issue.show_assigners.each do |assigner|
json.partial! "api/v1/users/simple_user", locals: {user: assigner}
end
@@ -44,4 +51,17 @@ json.operate_journals_count issue.operate_journals.size
json.attachments issue.attachments.each do |attachment|
json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
end
-json.pull_fixed issue.pull_attached_issues.where(fixed: true).present?
\ No newline at end of file
+json.pull_fixed issue.pull_attached_issues.where(fixed: true).present?
+json.root_id issue.root_id
+json.pm_issue_type issue.pm_issue_type
+json.pm_sprint_id issue.pm_sprint_id
+json.pm_project_id issue.pm_project_id
+json.time_scale issue.time_scale
+json.child_count issue.child_count
+json.project do
+ if issue.project.present? && issue.project&.owner.present?
+ json.partial! "api/v1/projects/detail", locals: {project: issue.project}
+ else
+ json.nil!
+ end
+end
\ No newline at end of file
diff --git a/app/views/api/v1/issues/_simple_detail.json.jbuilder b/app/views/api/v1/issues/_simple_detail.json.jbuilder
index 4a5a433be..15259baa5 100644
--- a/app/views/api/v1/issues/_simple_detail.json.jbuilder
+++ b/app/views/api/v1/issues/_simple_detail.json.jbuilder
@@ -6,9 +6,32 @@ json.tags issue.show_issue_tags.each do |tag|
json.partial! "api/v1/issues/issue_tags/simple_detail", locals: {tag: tag}
end
json.status_name issue.issue_status&.name
+json.status_id issue.status_id
+json.status do
+ if issue.issue_status.present?
+ json.partial! "api/v1/issues/statues/simple_detail", locals: {status: issue.issue_status}
+ else
+ json.nil!
+ end
+end
json.priority_name issue.priority&.name
+json.priority_id issue.priority_id
+json.priority do
+ if issue.priority.present?
+ json.partial! "api/v1/issues/issue_priorities/simple_detail", locals: {priority: issue.priority}
+ else
+ json.nil!
+ end
+end
json.milestone_name issue.version&.name
json.milestone_id issue.fixed_version_id
+json.root_id issue.root_id
+json.pm_issue_type issue.pm_issue_type
+json.pm_sprint_id issue.pm_sprint_id
+json.pm_project_id issue.pm_project_id
+json.time_scale issue.time_scale
+json.child_count issue.child_count
+
json.author do
if issue.user.present?
json.partial! "api/v1/users/simple_user", locals: {user: issue.user}
diff --git a/app/views/api/v1/issues/index.json.jbuilder b/app/views/api/v1/issues/index.json.jbuilder
index cde117fdc..30e26d2ca 100644
--- a/app/views/api/v1/issues/index.json.jbuilder
+++ b/app/views/api/v1/issues/index.json.jbuilder
@@ -1,12 +1,13 @@
json.total_issues_count @total_issues_count
json.opened_count @opened_issues_count
json.closed_count @closed_issues_count
+json.complete_count @complete_issues_count
json.total_count @issues.total_count
-json.has_created_issues @project.issues.size > 0
+json.has_created_issues @project.present? ? @project.issues.size > 0 : 0
json.issues @issues.each do |issue|
if params[:only_name].present?
json.(issue, :id, :subject, :project_issues_index)
else
- json.partial! "simple_detail", locals: {issue: issue}
+ json.partial! "api/v1/issues/simple_detail", locals: {issue: issue}
end
end
\ No newline at end of file
diff --git a/app/views/api/v1/issues/issue_priorities/_simple_detail.json.jbuilder b/app/views/api/v1/issues/issue_priorities/_simple_detail.json.jbuilder
index b7c37147a..f5cf659d8 100644
--- a/app/views/api/v1/issues/issue_priorities/_simple_detail.json.jbuilder
+++ b/app/views/api/v1/issues/issue_priorities/_simple_detail.json.jbuilder
@@ -1 +1 @@
-json.(priority, :id, :name)
+json.(priority, :id, :name,:pm_color)
diff --git a/app/views/api/v1/issues/issue_priorities/index.json.jbuilder b/app/views/api/v1/issues/issue_priorities/index.json.jbuilder
index c1b8ebb25..04bcd96ff 100644
--- a/app/views/api/v1/issues/issue_priorities/index.json.jbuilder
+++ b/app/views/api/v1/issues/issue_priorities/index.json.jbuilder
@@ -1,4 +1,4 @@
json.total_count @priorities.total_count
json.priorities @priorities.each do |priority|
- json.partial! "simple_detail", locals: {priority: priority}
+ json.partial! "api/v1/issues/issue_priorities/simple_detail", locals: {priority: priority}
end
\ No newline at end of file
diff --git a/app/views/api/v1/issues/issue_tags/index.json.jbuilder b/app/views/api/v1/issues/issue_tags/index.json.jbuilder
index 0bd055b57..83fee7f6b 100644
--- a/app/views/api/v1/issues/issue_tags/index.json.jbuilder
+++ b/app/views/api/v1/issues/issue_tags/index.json.jbuilder
@@ -1,8 +1,8 @@
json.total_count @issue_tags.total_count
json.issue_tags @issue_tags.each do |tag|
if params[:only_name]
- json.partial! "simple_detail", locals: {tag: tag}
+ json.partial! "api/v1/issues/issue_tags/simple_detail", locals: {tag: tag}
else
- json.partial! "detail", locals: {tag: tag}
+ json.partial! "api/v1/issues/issue_tags/detail", locals: {tag: tag}
end
end
\ No newline at end of file
diff --git a/app/views/api/v1/issues/journals/_detail.json.jbuilder b/app/views/api/v1/issues/journals/_detail.json.jbuilder
index 264997bbd..21040bc0a 100644
--- a/app/views/api/v1/issues/journals/_detail.json.jbuilder
+++ b/app/views/api/v1/issues/journals/_detail.json.jbuilder
@@ -17,7 +17,7 @@ else
json.notes journal.notes
json.comments_count journal.comments_count
json.children_journals journal.first_ten_children_journals.each do |journal|
- json.partial! "children_detail", journal: journal
+ json.partial! "api/v1/issues/journals/children_detail", journal: journal
end
json.attachments journal.attachments do |attachment|
json.partial! "api/v1/attachments/simple_detail", locals: {attachment: attachment}
diff --git a/app/views/api/v1/issues/journals/children_journals.json.jbuilder b/app/views/api/v1/issues/journals/children_journals.json.jbuilder
index c0cd04501..ddea195d7 100644
--- a/app/views/api/v1/issues/journals/children_journals.json.jbuilder
+++ b/app/views/api/v1/issues/journals/children_journals.json.jbuilder
@@ -1,4 +1,4 @@
json.total_count @journals.total_count
json.journals @journals do |journal|
- json.partial! "children_detail", journal: journal
+ json.partial! "api/v1/issues/journals/children_detail", journal: journal
end
\ No newline at end of file
diff --git a/app/views/api/v1/issues/journals/create.json.jbuilder b/app/views/api/v1/issues/journals/create.json.jbuilder
index 91f3f3174..a28523db1 100644
--- a/app/views/api/v1/issues/journals/create.json.jbuilder
+++ b/app/views/api/v1/issues/journals/create.json.jbuilder
@@ -1 +1 @@
-json.partial! "detail", journal: @object_result
\ No newline at end of file
+json.partial! "api/v1/issues/journals/detail", journal: @object_result
\ No newline at end of file
diff --git a/app/views/api/v1/issues/journals/index.json.jbuilder b/app/views/api/v1/issues/journals/index.json.jbuilder
index 453c39c59..b113f39a0 100644
--- a/app/views/api/v1/issues/journals/index.json.jbuilder
+++ b/app/views/api/v1/issues/journals/index.json.jbuilder
@@ -4,5 +4,5 @@ json.total_comment_journals_count @total_comment_journals_count
json.total_count @journals.total_count
json.journals @journals do |journal|
journal.associate_attachment_container
- json.partial! "detail", journal: journal
+ json.partial! "api/v1/issues/journals/detail", journal: journal
end
\ No newline at end of file
diff --git a/app/views/api/v1/issues/journals/update.json.jbuilder b/app/views/api/v1/issues/journals/update.json.jbuilder
index 91f3f3174..a28523db1 100644
--- a/app/views/api/v1/issues/journals/update.json.jbuilder
+++ b/app/views/api/v1/issues/journals/update.json.jbuilder
@@ -1 +1 @@
-json.partial! "detail", journal: @object_result
\ No newline at end of file
+json.partial! "api/v1/issues/journals/detail", journal: @object_result
\ No newline at end of file
diff --git a/app/views/api/v1/issues/statues/_simple_detail.json.jbuilder b/app/views/api/v1/issues/statues/_simple_detail.json.jbuilder
index f66b6c95a..c649fc37a 100644
--- a/app/views/api/v1/issues/statues/_simple_detail.json.jbuilder
+++ b/app/views/api/v1/issues/statues/_simple_detail.json.jbuilder
@@ -1 +1 @@
-json.(status, :id, :name)
+json.(status, :id, :name, :pm_color)
diff --git a/app/views/api/v1/issues/statues/index.json.jbuilder b/app/views/api/v1/issues/statues/index.json.jbuilder
index 9fb60acc2..bff73bd8d 100644
--- a/app/views/api/v1/issues/statues/index.json.jbuilder
+++ b/app/views/api/v1/issues/statues/index.json.jbuilder
@@ -1,4 +1,4 @@
json.total_count @statues.total_count
json.statues @statues.each do |status|
- json.partial! "simple_detail", locals: {status: status}
+ json.partial! "api/v1/issues/statues/simple_detail", locals: {status: status}
end
\ No newline at end of file
diff --git a/app/views/api/v1/project_datasets/index.json.jbuilder b/app/views/api/v1/project_datasets/index.json.jbuilder
new file mode 100644
index 000000000..29a84708d
--- /dev/null
+++ b/app/views/api/v1/project_datasets/index.json.jbuilder
@@ -0,0 +1,14 @@
+json.total_count @project_datasets.total_count
+json.project_datasets @project_datasets.each do |dataset|
+ json.(dataset, :id, :title, :description, :paper_content)
+ json.project do
+ json.partial! "api/v1/projects/simple_detail", project: dataset.project
+ end
+ if dataset.license.present?
+ json.license do
+ json.(dataset.license, :name, :content)
+ end
+ else
+ json.license nil
+ end
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/_detail.json.jbuilder b/app/views/api/v1/projects/_detail.json.jbuilder
new file mode 100644
index 000000000..03219f822
--- /dev/null
+++ b/app/views/api/v1/projects/_detail.json.jbuilder
@@ -0,0 +1,26 @@
+json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id, :is_public)
+json.mirror_url project.repository&.mirror_url
+json.type project.numerical_for_project_type
+json.praised project.praised_by?(current_user)
+json.last_update_time render_unix_time(project.updated_on)
+json.time_ago time_from_now(project.updated_on)
+json.language do
+ if project.project_language.blank?
+ json.nil!
+ else
+ json.id project.project_language.id
+ json.name project.project_language.name
+ end
+end
+json.category do
+ if project.project_category.blank?
+ json.nil!
+ else
+ json.id project.project_category.id
+ json.name project.project_category.name
+ end
+end
+json.topics project.project_topics.each do |topic|
+ json.(topic, :id, :name)
+end
+json.url project.full_url
\ No newline at end of file
diff --git a/app/views/api/v1/projects/_simple_detail.json.jbuilder b/app/views/api/v1/projects/_simple_detail.json.jbuilder
index 3eadaaf8f..15799aa13 100644
--- a/app/views/api/v1/projects/_simple_detail.json.jbuilder
+++ b/app/views/api/v1/projects/_simple_detail.json.jbuilder
@@ -1,7 +1,7 @@
if project.present?
json.type project.project_type
json.(project,
- :description, :forked_count, :forked_from_project_id, :identifier,
+ :id, :description, :forked_count, :forked_from_project_id, :identifier,
:issues_count, :pull_requests_count, :invite_code, :website, :platform,
:name, :open_devops, :praises_count, :is_public, :status, :watchers_count,
:ignore_id, :license_id, :project_category_id, :project_language_id)
diff --git a/app/views/api/v1/projects/actions/actions/index.json.jbuilder b/app/views/api/v1/projects/actions/actions/index.json.jbuilder
new file mode 100644
index 000000000..530cfc6af
--- /dev/null
+++ b/app/views/api/v1/projects/actions/actions/index.json.jbuilder
@@ -0,0 +1,4 @@
+json.total_count @data.size
+json.files @data.each do |file|
+ json.name file["Name"]
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/actions/runs/index.json.jbuilder b/app/views/api/v1/projects/actions/runs/index.json.jbuilder
new file mode 100644
index 000000000..9122ef9cf
--- /dev/null
+++ b/app/views/api/v1/projects/actions/runs/index.json.jbuilder
@@ -0,0 +1,23 @@
+json.total_data @result_object[:total_data].to_i
+if @result_object[:data]["Runs"].present?
+ json.runs @result_object[:data]["Runs"].each do |run|
+ json.workflow run["WorkflowID"]
+ json.index run["Index"]
+ json.title run["Title"]
+ json.trigger_user do
+ json.partial! 'api/v1/users/commit_user', locals: { user: render_cache_commit_author(run['TriggerUser']), name: run['TriggerUser']['Name'] }
+ end
+
+ if run["Ref"].starts_with?("refs/tags")
+ json.ref run["Ref"].gsub!("/refs/tags/", "")
+ else
+ json.ref run["Ref"].gsub!("refs/heads/", "")
+ end
+
+ json.status run["Status"]
+ json.time_ago time_from_now(run["Created"])
+ json.holding_time run["Status"] == 6 ? Time.now.to_i - run["Created"] : run["Stopped"] - run["Created"]
+ end
+else
+ json.runs []
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/actions/runs/job_show.json.jbuilder b/app/views/api/v1/projects/actions/runs/job_show.json.jbuilder
new file mode 100644
index 000000000..bf5ce048a
--- /dev/null
+++ b/app/views/api/v1/projects/actions/runs/job_show.json.jbuilder
@@ -0,0 +1,16 @@
+json.state do
+ json.run do
+ json.title @result_object["state"]["run"]["title"]
+ json.status @result_object["state"]["run"]["status"]
+ json.done @result_object["state"]["run"]["done"]
+ json.jobs @result_object["state"]["run"]["jobs"]
+ json.current_job do
+ json.title @result_object["state"]["currentJob"]["title"]
+ json.detail @result_object["state"]["currentJob"]["detail"]
+ json.steps @result_object["state"]["currentJob"]["steps"]
+ end
+ end
+end
+json.logs do
+ json.steps_log @result_object["logs"]["stepsLog"]
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/branches/_simple_gitea_detail.json.jbuilder b/app/views/api/v1/projects/branches/_simple_gitea_detail.json.jbuilder
index c9235bdb4..ff0f0618a 100644
--- a/app/views/api/v1/projects/branches/_simple_gitea_detail.json.jbuilder
+++ b/app/views/api/v1/projects/branches/_simple_gitea_detail.json.jbuilder
@@ -23,4 +23,14 @@ json.commit_time branch['commit']['timestamp']
json.default_branch default_branch || nil
json.http_url render_http_url(@project)
json.zip_url render_zip_url(@owner, @project.repository, branch['name'])
-json.tar_url render_tar_url(@owner, @project.repository, branch['name'])
\ No newline at end of file
+json.tar_url render_tar_url(@owner, @project.repository, branch['name'])
+json.branch_id branch['id']
+json.is_deleted branch['is_deleted']
+json.deleted_unix branch['deleted_unix']
+json.deleted_by do
+ if branch['is_deleted']
+ json.partial! 'api/v1/users/commit_user', locals: { user: render_cache_commit_author(branch['deleted_by']), name: branch['deleted_by']['name'] }
+ else
+ json.nil!
+ end
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/commits/recent.json.jbuilder b/app/views/api/v1/projects/commits/recent.json.jbuilder
new file mode 100644
index 000000000..38a367d4c
--- /dev/null
+++ b/app/views/api/v1/projects/commits/recent.json.jbuilder
@@ -0,0 +1,16 @@
+json.total_count @result_object[:total_data].to_i
+json.ssh_url @object_detail['ssh_url']
+json.clone_url @object_detail['clone_url']
+json.commits @result_object[:data].each do |commit|
+ json.sha commit['sha']
+ json.author do
+ json.partial! 'api/v1/users/commit_user', locals: { user: render_cache_commit_author(commit['commit']['author']), name: commit['commit']['author']['name'] }
+ end
+
+ json.committer do
+ json.partial! 'api/v1/users/commit_user', locals: { user: render_cache_commit_author(commit['commit']['committer']), name: commit['commit']['committer']['name'] }
+ end
+ json.commit_message commit['commit']['message']
+ json.time_from_now time_from_now(commit['created'])
+ json.parent_shas commit['parents'].map{|x|x['sha']}
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/compare.json.jbuilder b/app/views/api/v1/projects/compare.json.jbuilder
index 2d20c51e0..f45bceb86 100644
--- a/app/views/api/v1/projects/compare.json.jbuilder
+++ b/app/views/api/v1/projects/compare.json.jbuilder
@@ -14,5 +14,9 @@ json.commits @result_object['Commits'] do |commit|
json.parent_shas commit['Sha']['ParentShas']
end
json.diff do
- json.partial! "api/v1/projects/simple_gitea_diff_detail", diff: @result_object['Diff']
+ if @result_object['Diff'].present?
+ json.partial! "api/v1/projects/simple_gitea_diff_detail", diff: @result_object['Diff']
+ else
+ json.nil!
+ end
end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/datasets/show.json.jbuilder b/app/views/api/v1/projects/datasets/show.json.jbuilder
new file mode 100644
index 000000000..6b18d8015
--- /dev/null
+++ b/app/views/api/v1/projects/datasets/show.json.jbuilder
@@ -0,0 +1,6 @@
+json.(@project_dataset, :id, :title, :description, :license_id, :paper_content)
+json.license_name @project_dataset&.license&.name
+json.attachment_total_count @attachments.total_count
+json.attachments @attachments do |at|
+ json.partial! "api/v1/attachments/detail", locals: {attachment: at}
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/git/trees.json.jbuilder b/app/views/api/v1/projects/git/trees.json.jbuilder
index 0eb08f048..b560fff2b 100644
--- a/app/views/api/v1/projects/git/trees.json.jbuilder
+++ b/app/views/api/v1/projects/git/trees.json.jbuilder
@@ -1,6 +1,6 @@
-json.total_count @result_object['total_count']
-json.sha @result_object['sha']
-json.entries @result_object['tree'].each do |entry|
+json.total_count @result_object[:data]['total_count'].to_i
+json.sha @result_object[:data]['sha']
+json.entries @result_object[:data]['tree'].each do |entry|
json.name entry['path']
json.mode entry['mode']
json.type entry['type'] === 'blob' ? 'file' : 'dir'
diff --git a/app/views/api/v1/projects/pipelines/build_node.yaml.erb b/app/views/api/v1/projects/pipelines/build_node.yaml.erb
new file mode 100644
index 000000000..ded111099
--- /dev/null
+++ b/app/views/api/v1/projects/pipelines/build_node.yaml.erb
@@ -0,0 +1,9 @@
+steps:
+ - name: <%=self.node.name %>
+ uses: <%=self.full_name %>
+ <%if self.action_node_inputs.present? %>
+ with:
+ <% self.action_node_inputs.each do |input| %>
+ <%=input.name %>: ''
+ <%end %>
+ <%end %>
\ No newline at end of file
diff --git a/app/views/api/v1/projects/pipelines/build_pipeline.yaml.erb b/app/views/api/v1/projects/pipelines/build_pipeline.yaml.erb
new file mode 100644
index 000000000..149d7a6be
--- /dev/null
+++ b/app/views/api/v1/projects/pipelines/build_pipeline.yaml.erb
@@ -0,0 +1,62 @@
+# action name
+name: <%=@pipeline_name %>
+
+# 什么时候触发这个workflow
+on:
+ <%@on_nodes.each do |node| %>
+ <%if node.name.to_s.include?("on-push") %>
+ push:
+ <% node.input_values.each_key do |key| %>
+ <%=key %>:
+ <% if node.input_values[key].blank? %>
+ - *
+ <% else %>
+ <% node.input_values[key].to_s.split(",").each do |val| %>
+ - <%=val %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <%if node.name.to_s.include?("on-pull_request") %>
+ pull_request:
+ <% node.input_values.each_key do |key| %>
+ <%=key %>:
+ <% if node.input_values[key].blank? %>
+ - *
+ <% else %>
+ <% node.input_values[key].to_s.split(",").each do |val| %>
+ - <%=val %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <%if node.name.to_s.include?("on-schedule") %>
+ schedule:
+ <% node.input_values.each_key do |key| %>
+ - <%=key %>: "<%=node.input_values[key] %>"
+ <% end %>
+ <% end %>
+ <% end %>
+
+jobs:
+ job1:
+ # 运行环境
+ runs-on: 'ubuntu-latest'
+ steps:
+ <%@steps_nodes.each do |node| %>
+ - name: <%=node.cust_name || node.name %>
+ <% if node.name !="shell" %>
+ uses: <%=node.full_name %>
+ <% end %>
+ <%if node.input_values.present? %>
+ with:
+ <% node.input_values.each_key do |key| %>
+ <%=key %>: '<%=node.input_values[key] %>'
+ <%end %>
+ <%end %>
+ <%if node.run_values.present? %>
+ <% node.run_values.each_key do |key| %>
+ <%=key %>: '<%=node.run_values[key] %>'
+ <%end %>
+ <%end %>
+ <% end %>
diff --git a/app/views/api/v1/projects/pipelines/index.json.jbuilder b/app/views/api/v1/projects/pipelines/index.json.jbuilder
new file mode 100644
index 000000000..7c3f2a405
--- /dev/null
+++ b/app/views/api/v1/projects/pipelines/index.json.jbuilder
@@ -0,0 +1,8 @@
+json.status 0
+json.message "success"
+
+json.pipelines @pipelines.each do |pip|
+ json.extract! pip, :id, :pipeline_name, :pipeline_status, :description, :file_name, :is_graphic_design,
+ :repo_name, :repo_identifier, :branch, :event, :sha, :disable, :json, :yaml, :created_at, :updated_at
+ # json.project
+end
diff --git a/app/views/api/v1/projects/pipelines/show.json.jbuilder b/app/views/api/v1/projects/pipelines/show.json.jbuilder
new file mode 100644
index 000000000..d82015ade
--- /dev/null
+++ b/app/views/api/v1/projects/pipelines/show.json.jbuilder
@@ -0,0 +1,5 @@
+json.status 0
+json.message "success"
+json.extract! @pipeline, :id, :pipeline_name, :pipeline_status, :description, :file_name, :is_graphic_design,
+ :repo_name, :repo_identifier, :branch, :event, :sha, :disable, :json, :yaml, :created_at, :updated_at
+# json.project
\ No newline at end of file
diff --git a/app/views/api/v1/projects/pipelines/test.yaml.erb b/app/views/api/v1/projects/pipelines/test.yaml.erb
new file mode 100644
index 000000000..3ec890230
--- /dev/null
+++ b/app/views/api/v1/projects/pipelines/test.yaml.erb
@@ -0,0 +1,89 @@
+name: Check dist<%= @name %>
+
+# action name
+name: Test with Junit
+
+# 什么时候触发这个workflow
+on:
+ # push 到master分之的时候 这里可以指定多个
+ #push:
+ # branches:
+ # - master
+# paths-ignore:
+# - '**.md'
+ # pull request 到master分之的时候, 这里可以指定多个
+ #pull_request:
+ # branches:
+ # - master
+# paths-ignore:
+# - '**.md'
+ # 定时调度执行
+ schedule:
+ - cron: '26 10,11 * * *'
+env:
+ https_proxy: http://172.20.32.253:3128
+ http_proxy: http://172.20.32.253:3128
+
+# 一个workflow可以由多个job组成,多个job可以并行运行
+jobs:
+ junit:
+ strategy:
+ matrix:
+ # 指定jdk 版本。可以指定多个版本 比如[8,11,17]
+ java: [11]
+ # 指定运行 os 版本 也是多个
+ os: [ 'ubuntu-latest' ]
+ # 运行环境,这里就是上面定义的多个 os
+ runs-on: 'ubuntu-latest'
+
+ steps:
+ # 将job的工作目录指向$GITHUB_WORSPACES checkout@v2比较旧不推荐使用
+ - name: Checkout codes
+ uses: actions/checkout@v3
+ #- name: Install Java and Maven
+ # uses: actions/setup-java@v3
+ # with:
+ # java-version: '11'
+ # distribution: 'temurin'
+ # 设置jdk环境
+ - name: download latest temurin JDK
+ id: download_latest_jdk
+ env:
+ HTTPS_PROXY: http://172.20.32.253:3128
+ HTTP_PROXY: http://172.20.32.253:3128
+ #run: curl -o https://testgitea2.trustie.net/xxq250/licensee-identify-api-sss/raw/branch/master/openlogic-openjdk-11.0.22+7-linux-x64.tar.gz
+ run: wget -O $RUNNER_TEMP/java_package.tar.gz "http://172.20.32.202:10082/xxq250/licensee-identify-api-sss/raw/branch/master/OpenJDK11U-jdk_x64_linux_hotspot_11.0.22_7.tar.gz"
+ - uses: actions/setup-java@v4
+ with:
+ distribution: 'jdkfile'
+ jdkFile: ${{ runner.temp }}/java_package.tar.gz
+ java-version: '11.0.0'
+ architecture: x64
+ mvn-toolchain-vendor: 'Oracle'
+ # 设置maven 仓库缓存 避免每次构建时都重新下载依赖
+ - name: Cache local Maven repository
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Test java version
+ run: java -version
+ - name: Set up Maven
+ uses: stCarolas/setup-maven@v5
+ with:
+ maven-version: 3.8.2
+ - name: Setup Maven mirrors
+ uses: s4u/maven-settings-action@v3.0.0
+ with:
+ mirrors: '[{"id": "alimaven", "name": "aliyun maven", "mirrorOf": "central", "url": "http://172.20.32.181:30005/repository/aliyun-maven/"}]'
+ - name: env show
+ run: env
+ - name: cat maven-settings.xml
+ run: cat /root/.m2/settings.xml
+ - name: Test with Maven
+ run: mvn clean test -B -U
+ env:
+ https_proxy: http://172.20.32.253:3128
+ http_proxy: http://172.20.32.253:3128
diff --git a/app/views/api/v1/projects/sync_repositories/branches.json.jbuilder b/app/views/api/v1/projects/sync_repositories/branches.json.jbuilder
new file mode 100644
index 000000000..f040c6476
--- /dev/null
+++ b/app/views/api/v1/projects/sync_repositories/branches.json.jbuilder
@@ -0,0 +1,12 @@
+json.total_count @group_sync_repository_branch.count
+json.sync_repository_branches @group_sync_repository_branch.each do |item|
+ json.gitlink_branch_name item.gitlink_branch_name
+ json.external_branch_name item.external_branch_name
+ branches = @sync_repository_branches.joins(:sync_repository).where(sync_repositories: {type: item.type}, gitlink_branch_name: item.gitlink_branch_name, external_branch_name: item.external_branch_name).order(updated_at: :desc)
+ branch = branches.first
+ json.type branch&.sync_repository&.type
+ json.sync_time branch.sync_time.present? ? branch.sync_time.strftime("%Y-%m-%d %H:%M:%S") : nil
+ json.sync_status branch.sync_status
+ json.enable branch.enable
+ json.reposync_branch_ids branches.pluck(:reposync_branch_id)
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/sync_repositories/create.json.jbuilder b/app/views/api/v1/projects/sync_repositories/create.json.jbuilder
new file mode 100644
index 000000000..20bb70c66
--- /dev/null
+++ b/app/views/api/v1/projects/sync_repositories/create.json.jbuilder
@@ -0,0 +1,5 @@
+json.gitlink_repo_address "#{EduSetting.get("gitlink_repo_domain")}/#{@project.owner&.login}/#{@project.identifier}.git"
+json.external_repo_address @sync_repository1.external_repo_address
+json.sync_granularity @sync_repository1.sync_granularity
+json.gitlink_branch_name @sync_repository_branch1&.gitlink_branch_name
+json.external_branch_name @sync_repository_branch1&.external_branch_name
\ No newline at end of file
diff --git a/app/views/api/v1/projects/sync_repositories/history.json.jbuilder b/app/views/api/v1/projects/sync_repositories/history.json.jbuilder
new file mode 100644
index 000000000..01546b292
--- /dev/null
+++ b/app/views/api/v1/projects/sync_repositories/history.json.jbuilder
@@ -0,0 +1,12 @@
+json.total_count @total_count
+json.gitlink_branch_name @branch&.gitlink_branch_name
+json.external_type @branch&.sync_repository&.type
+json.external_branch_name @branch&.external_branch_name
+json.logs @reposync_branch_logs.each do |log|
+ type = log['repo_name'].start_with?('gitee') ? 'gitee' : 'github'
+ json.change_from log['sync_direct'] == "to_inter" ? type : 'gitlink'
+ json.commit_id log['commit_id']
+ json.sync_time log['update_at']
+ json.log log['log']
+ json.sync_status log['log'].include?("************ 分支同步完成 ************") ? 'success' : "failure"
+end
\ No newline at end of file
diff --git a/app/views/api/v1/projects/sync_repositories/index.json.jbuilder b/app/views/api/v1/projects/sync_repositories/index.json.jbuilder
new file mode 100644
index 000000000..84bbdde5e
--- /dev/null
+++ b/app/views/api/v1/projects/sync_repositories/index.json.jbuilder
@@ -0,0 +1,8 @@
+json.total_count @group_sync_repository.keys.count
+json.sync_repositories @group_sync_repository.each do |key|
+ json.type key[0][0]
+ json.external_repo_address key[0][1]
+ json.sync_granularity key[0][2]
+ json.external_token key[0][3]
+ json.sync_repository_ids @sync_repositories.where(type: key[0][0], external_repo_address: key[0][1], sync_granularity: key[0][2]).pluck(:id)
+end
\ No newline at end of file
diff --git a/app/views/api/v1/users/_simple_user.json.jbuilder b/app/views/api/v1/users/_simple_user.json.jbuilder
index ef45bcd94..5e8e970e7 100644
--- a/app/views/api/v1/users/_simple_user.json.jbuilder
+++ b/app/views/api/v1/users/_simple_user.json.jbuilder
@@ -3,7 +3,7 @@ if user.present?
json.type user.type
json.name user.real_name
json.login user.login
- json.image_url url_to_avatar(user)
+ json.image_url Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(user).to_s
else
json.nil!
end
\ No newline at end of file
diff --git a/app/views/organizations/organization_users/_detail.json.jbuilder b/app/views/organizations/organization_users/_detail.json.jbuilder
index c7572971b..7590da02e 100644
--- a/app/views/organizations/organization_users/_detail.json.jbuilder
+++ b/app/views/organizations/organization_users/_detail.json.jbuilder
@@ -1,7 +1,14 @@
-json.id org_user.id
+json.id user&.id
json.user do
- json.partial! "organizations/user_detail", user: org_user.user
+ json.partial! "organizations/user_detail", user: user
end
-json.team_names org_user.teams.pluck(:nickname)
-json.created_at org_user.created_at.strftime("%Y-%m-%d")
+json.team_names user.teams.where("teams.organization_id=?", organization.id).pluck(:nickname)
+join_date = if user.organization_users.find_by(:organization_id => organization.id).present?
+ user.organization_users.find_by(:organization_id => organization.id).created_at.strftime("%Y-%m-%d")
+ elsif user.members.joins(:project).find_by(project: organization.projects).present?
+ user.members.joins(:project).find_by(project: organization.projects).created_on.strftime("%Y-%m-%d")
+ else
+ user.created_on.strftime("%Y-%m-%d")
+ end
+json.created_at join_date
diff --git a/app/views/organizations/organization_users/index.json.jbuilder b/app/views/organizations/organization_users/index.json.jbuilder
index 9f1f278bc..9241af184 100644
--- a/app/views/organizations/organization_users/index.json.jbuilder
+++ b/app/views/organizations/organization_users/index.json.jbuilder
@@ -1,5 +1,5 @@
-json.total_count @organization_users.total_count
-json.organization_users @organization_users do |org_user|
- next if org_user.user.blank?
- json.partial! "detail", org_user: org_user, organization: @organization
+json.total_count @users.total_count
+json.organization_users @users do |user|
+ next if user.blank?
+ json.partial! "detail", user: user, organization: @organization
end
diff --git a/app/views/organizations/projects/index.json.jbuilder b/app/views/organizations/projects/index.json.jbuilder
index 5a4615fc7..c395732f1 100644
--- a/app/views/organizations/projects/index.json.jbuilder
+++ b/app/views/organizations/projects/index.json.jbuilder
@@ -6,4 +6,24 @@ json.projects @projects.each do |project|
json.praised project.praised_by?(current_user)
json.last_update_time render_unix_time(project.updated_on)
json.time_ago time_from_now(project.updated_on)
+ json.language do
+ if project.project_language.blank?
+ json.nil!
+ else
+ json.id project.project_language.id
+ json.name project.project_language.name
+ end
+ end
+ json.category do
+ if project.project_category.blank?
+ json.nil!
+ else
+ json.id project.project_category.id
+ json.name project.project_category.name
+ end
+ end
+ json.topics project.project_topics.each do |topic|
+ json.(topic, :id, :name)
+ end
+ json.url project.full_url
end
\ No newline at end of file
diff --git a/app/views/projects/index.json.jbuilder b/app/views/projects/index.json.jbuilder
index d3b96ab18..a07dd6c1a 100644
--- a/app/views/projects/index.json.jbuilder
+++ b/app/views/projects/index.json.jbuilder
@@ -17,6 +17,7 @@ json.projects @projects do |project|
json.forked_from_project_id project.forked_from_project_id
json.open_devops project.open_devops?
json.platform project.platform
+ json.has_dataset project.has_menu_permission("dataset") && project.project_dataset.present?
json.author do
if project.educoder?
project_educoder = project.project_educoder
diff --git a/app/views/repositories/detail.json.jbuilder b/app/views/repositories/detail.json.jbuilder
index d4299a1ff..0e5d850a5 100644
--- a/app/views/repositories/detail.json.jbuilder
+++ b/app/views/repositories/detail.json.jbuilder
@@ -1,3 +1,4 @@
+json.empty @result[:repo]["empty"]
json.content @project.content
json.website @project.website
json.lesson_url @project.lesson_url
diff --git a/app/views/settings/show.json.jbuilder b/app/views/settings/show.json.jbuilder
index 1027be670..c3e515fd2 100644
--- a/app/views/settings/show.json.jbuilder
+++ b/app/views/settings/show.json.jbuilder
@@ -36,7 +36,8 @@ json.setting do
json.subject_banner_url default_setting.subject_banner_url&.[](1..-1)
json.course_banner_url default_setting.course_banner_url&.[](1..-1)
- json.competition_banner_url default_setting.competition_banner_url&.[](1..-1)
+ json.competition_banner_url EduSetting.get("competition_banner_url").to_s
+ json.competition_banner_href EduSetting.get("competition_banner_href").to_s
json.moop_cases_banner_url default_setting.moop_cases_banner_url&.[](1..-1)
json.oj_banner_url default_setting.oj_banner_url&.[](1..-1)
diff --git a/app/views/template_message_settings/_detail.json.jbuilder b/app/views/template_message_settings/_detail.json.jbuilder
index d85a4c4ea..5f6a0bf62 100644
--- a/app/views/template_message_settings/_detail.json.jbuilder
+++ b/app/views/template_message_settings/_detail.json.jbuilder
@@ -3,6 +3,7 @@ json.type_name type.constantize.type_name
json.total_settings_count count
json.settings do
json.array! type.constantize.openning.limit(100).each do |setting|
- json.(setting, :name, :key, :notification_disabled, :email_disabled)
+ json.(setting, :name, :key, :email_disabled)
+ json.notification_disabled false
end
end
\ No newline at end of file
diff --git a/app/views/version_releases/_version_release.json.jbuilder b/app/views/version_releases/_version_release.json.jbuilder
index 1ccdbe617..9835db152 100644
--- a/app/views/version_releases/_version_release.json.jbuilder
+++ b/app/views/version_releases/_version_release.json.jbuilder
@@ -16,6 +16,12 @@ json.user_login user&.login
json.image_url user.present? ? url_to_avatar(user) : ""
json.attachments do
json.array! version.try(:attachments) do |attachment|
- json.partial! "attachments/attachment_simple", locals: {attachment: attachment}
+ # json.partial! "attachments/attachment_simple", locals: {attachment: attachment}
+ json.id attachment.id
+ json.title attachment.title
+ json.filesize number_to_human_size attachment.filesize
+ json.description attachment.description
+ json.is_pdf attachment.is_pdf?
+ json.url "/#{@owner.login}/#{@repository.identifier}/releases/download/#{version&.tag_name}/#{attachment.filename}"
end
end
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index c36279e38..4cbefed14 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -41,7 +41,7 @@ module Gitlink
config.middleware.insert_before 0, Rack::Cors do
allow do
# origins '*'
- origins /http:\/\/localhost(:\d+)?\z/, /^(http|https):\/\/(.*(gitlink.org.cn))$/, /^(http|https):\/\/(.*(trustie.net))$/
+ origins /http:\/\/localhost(:\d+)?\z/, /http:\/\/172.20.32.201(:\d+)?\z/, /http:\/\/172.20.32.202(:\d+)?\z/, /^(http|https):\/\/(.*(gitlink.org.cn))$/, /^(http|https):\/\/(.*(trustie.net))$/
# location of your api
resource '/*', :headers => :any, :methods => [:get, :post, :delete, :options, :put, :patch], credentials: true
end
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 1adbdde7c..199481259 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -62,8 +62,8 @@ Rails.application.configure do
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
-
+ config.file_watcher = ActiveSupport::FileUpdateChecker
+ config.reload_classes_only_on_change = false
config.action_controller.perform_caching = true
config.action_mailer.delivery_method = :smtp
diff --git a/config/initializers/sonarqube.rb b/config/initializers/sonarqube.rb
new file mode 100644
index 000000000..30754885c
--- /dev/null
+++ b/config/initializers/sonarqube.rb
@@ -0,0 +1,8 @@
+sonarqube_config = Rails.application.config_for(:configuration)['sonarqube']
+
+Sonarqube.configure do |config|
+ config.endpoint = sonarqube_config["url"] # API endpoint URL, default: ENV['SONARQUBE_API_ENDPOINT']
+ config.private_token = sonarqube_config["secret"] # user's private token, default: ENV['SONARQUBE_API_PRIVATE_TOKEN']
+ # Optional
+ # config.user_agent = 'Custom User Agent' # user agent, default: 'Sonarqube Ruby Gem [version]'
+end
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 7694a77c0..8c7a59b53 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -19,11 +19,13 @@ Rails.application.routes.draw do
get 'attachments/entries/get_file', to: 'attachments#get_file'
get 'attachments/download/:id', to: 'attachments#show'
get 'attachments/download/:id/:filename', to: 'attachments#show'
+ get ':owner/:repo/releases/download/:tag_name/:filename', to: 'version_releases#download', constraints: { repo: /[^\/]+/, tag_name: /[^\/]+/, filename: /[^\/]+/ }
get 'check_pr_url',to: "settings#check_url"
# get 'auth/qq/callback', to: 'oauth/qq#create'
get 'auth/failure', to: 'oauth/base#auth_failure'
get 'auth/cas/callback', to: 'oauth/cas#create'
+ get 'auth/acge/callback', to: "oauth/acge#create"
get 'auth/:provider/callback', to: 'oauth/callbacks#create'
get 'oauth/bind', to: 'oauth/educoder#bind'
@@ -127,7 +129,6 @@ Rails.application.routes.draw do
# blockchain related routes
get 'users/blockchain/balance', to: 'users#blockchain_balance'
- get 'projects/mp_show', to: 'projects#mp_show'
post 'users/blockchain/balance_project', to: 'users#blockchain_balance_one_project'
post 'users/blockchain/transfer', to: 'users#blockchain_transfer'
post 'users/blockchain/exchange', to: 'users#blockchain_exchange'
@@ -509,6 +510,19 @@ Rails.application.routes.draw do
end
end
+ # scope module: :action do
+ #
+ # end
+
+ namespace :action do
+ resources :nodes do
+ resources :node_inputs
+ resources :node_selects
+ end
+ resources :node_types
+ resources :templates
+ end
+
# Project Area START
scope "/:owner/:repo",constraints: { repo: /[^\/]+/ } do
scope do
@@ -795,6 +809,8 @@ Rails.application.routes.draw do
namespace :admins do
mount Sidekiq::Web => '/sidekiq'
get '/', to: 'dashboards#index'
+ get '/baidu_tongji', to: 'dashboards#baidu_tongji'
+ get '/baidu_tongji_auth', to: 'dashboards#baidu_tongji_auth'
namespace :topic do
resources :activity_forums
resources :banners
@@ -820,6 +836,7 @@ Rails.application.routes.draw do
resources :identity_verifications
resources :site_pages
resources :page_themes
+ resources :issues_rank, only: [:index]
resources :projects_rank, only: [:index]
resources :sites
resources :edu_settings
@@ -1091,15 +1108,15 @@ Rails.application.routes.draw do
resources :sub_repertoires, only: [:index, :create, :edit, :update, :destroy]
resources :tag_repertoires, only: [:index, :create, :edit, :update, :destroy]
- resources :salesmans, only: [:index, :create, :edit, :update, :destroy] do
- post :batch_add, on: :collection
- end
- resources :salesman_channels, only: [:index, :create, :edit, :update, :destroy] do
- post :batch_add, on: :collection
- end
- resources :salesman_customers, only: [:index, :create, :edit, :update, :destroy] do
- post :batch_add, on: :collection
- end
+ # resources :salesmans, only: [:index, :create, :edit, :update, :destroy] do
+ # post :batch_add, on: :collection
+ # end
+ # resources :salesman_channels, only: [:index, :create, :edit, :update, :destroy] do
+ # post :batch_add, on: :collection
+ # end
+ # resources :salesman_customers, only: [:index, :create, :edit, :update, :destroy] do
+ # post :batch_add, on: :collection
+ # end
end
diff --git a/config/routes/api.rb b/config/routes/api.rb
index baa66412e..998d699bd 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -1,15 +1,54 @@
defaults format: :json do
- namespace :api do
- namespace :v1 do
+ namespace :api do
+ namespace :pm do
+ resources :issues do
+ collection do
+ patch :batch_update
+ delete :batch_destroy
+ get :priorities
+ get :tags
+ get :statues
+ end
+ member do
+ get :link_index
+ get :parent_issues
+ end
+ resources :issue_links
- resources :users, only: [:index] do
+ resources :journals do
+ member do
+ get :children_journals
+ end
+ end
+ end
+ resources :issue_tags
+ resources :sprint_issues, only: [:index] do
+ collection do
+ get :statistics
+
+ get :burndown_charts
+ post :complete
+ end
+ end
+ resources :projects do
+ collection do
+ get :convert
+ get :issues_count
+ get :statistics
+ get :polyline
+ end
+ end
+ end
+
+ namespace :v1 do
+ resources :users, only: [:index] do
collection do
post :check_user_id
post :check_user_login
end
end
- scope ':owner' do
+ scope ':owner' do
resource :users, path: '/', only: [:update, :edit, :destroy] do
collection do
get :send_email_vefify_code
@@ -37,6 +76,21 @@ defaults format: :json do
collection do
get :compare
get :blame
+
+ end
+ end
+ resource :sonarqubes, only: [:index] do
+ collection do
+ get :issues_search
+ get :ce_component
+ get :sources_issue_snippet
+ get :rules_show
+ get :measures_search_history
+ get :measures_component
+
+ post :sonar_initialize
+ post :insert_file
+
end
end
@@ -44,6 +98,7 @@ defaults format: :json do
collection do
patch :batch_update
delete :batch_destroy
+ post :pm_create
end
member do
@@ -55,30 +110,48 @@ defaults format: :json do
end
end
end
+
+
scope module: :issues do
- resources :issue_tags, except: [:new, :edit] do
- collection do
- get :pm_index
- end
- end
+ resources :issue_tags, except: [:new, :edit]
resources :milestones, except: [:new, :edit]
- resources :issue_statues, only: [:index], controller: '/api/v1/issues/statues' do
- collection do
- get :pm_index
- end
- end
+ resources :issue_statues, only: [:index], controller: '/api/v1/issues/statues'
resources :issue_authors, only: [:index], controller: '/api/v1/issues/authors'
resources :issue_assigners, only: [:index], controller: '/api/v1/issues/assigners'
- resources :issue_priorities, only: [:index] do
- collection do
- get :pm_index
- end
- end
+ resources :issue_priorities, only: [:index]
end
# projects文件夹下的
scope module: :projects do
- resources :pulls, module: 'pulls' do
+ resources :sync_repositories, only: [:create, :index] do
+ collection do
+ post :update_info
+ post :sync
+ get :branches
+ post :change_enable
+ post :unbind
+ get :history
+ post :create_branch
+ end
+ end
+ resource :dataset, only: [:create, :update, :show]
+ resources :actions, module: 'actions' do
+ collection do
+ post :disable
+ post :enable
+ resources :runs, only: [:index] do
+ post '/jobs/:job', to: 'runs#job_show'
+ post '/rerun', to: 'runs#rerun'
+ post '/jobs/:job/rerun', to: 'runs#job_rerun'
+ get '/jobs/:job/logs', to: 'runs#job_logs'
+ end
+ end
+ end
+ resources :pipelines do
+ post :build_yaml, on: :collection
+ post :save_yaml, on: :collection
+ end
+ resources :pulls, module: 'pulls' do
resources :versions, only: [:index] do
member do
get :diff
@@ -97,7 +170,10 @@ defaults format: :json do
end
resources :branches, param: :name, only:[:index, :create, :destroy] do
collection do
+ get :gitee
+ get :github
get :all
+ post :restore
patch :update_default_branch
end
end
@@ -107,7 +183,11 @@ defaults format: :json do
delete 'tags/*name', to: "tags#destroy", via: :all
get 'tags/*name', to: "tags#show", via: :all
- resources :commits, only: [:index]
+ resources :commits, only: [:index] do
+ collection do
+ get :recent
+ end
+ end
resources :code_stats, only: [:index]
resources :contributors, only: [:index] do
collection do
@@ -130,7 +210,8 @@ defaults format: :json do
resources :projects, only: [:index]
resources :project_topics, only: [:index, :create, :destroy]
-
+ resources :project_datasets, only: [:index]
+ resources :gitlink_competition_applies, only: [:create]
end
end
diff --git a/config/sidekiq_cron.yml b/config/sidekiq_cron.yml
index 0ec8f997e..72a69bbad 100644
--- a/config/sidekiq_cron.yml
+++ b/config/sidekiq_cron.yml
@@ -5,10 +5,15 @@ sync_gitea_repo_update_time:
delay_expired_issue:
cron: "0 0 * * *"
- class: "DelayExpiredIssueJob"
+ class: "DelayExpiredIssueAndMilestoneJob"
queue: message
create_daily_project_statistics:
cron: "0 1 * * *"
class: "DailyProjectStatisticsJob"
- queue: cache
\ No newline at end of file
+ queue: cache
+
+daily_platform_statistics:
+ cron: "0 1 * * *"
+ class: "DailyPlatformStatisticsJob"
+ queue: default
\ No newline at end of file
diff --git a/db/migrate/20231031070603_add_pm_issue_type_to_issues.rb b/db/migrate/20231031070603_add_pm_issue_type_to_issues.rb
new file mode 100644
index 000000000..846a760f1
--- /dev/null
+++ b/db/migrate/20231031070603_add_pm_issue_type_to_issues.rb
@@ -0,0 +1,5 @@
+class AddPmIssueTypeToIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_column :issues, :pm_issue_type, :integer
+ end
+end
diff --git a/db/migrate/20231101090823_add_pm_project_id.rb b/db/migrate/20231101090823_add_pm_project_id.rb
new file mode 100644
index 000000000..81ba7130b
--- /dev/null
+++ b/db/migrate/20231101090823_add_pm_project_id.rb
@@ -0,0 +1,5 @@
+class AddPmProjectId < ActiveRecord::Migration[5.2]
+ def change
+ add_column :issue_tags, :pm_project_id, :integer
+ end
+end
diff --git a/db/migrate/20231107003833_add_hour_to_issues.rb b/db/migrate/20231107003833_add_hour_to_issues.rb
new file mode 100644
index 000000000..d0a3e4856
--- /dev/null
+++ b/db/migrate/20231107003833_add_hour_to_issues.rb
@@ -0,0 +1,5 @@
+class AddHourToIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_column :issues, :time_scale, :decimal, precision: 10, scale: 2, default: 0.00
+ end
+end
diff --git a/db/migrate/20231107072541_create_pm_links.rb b/db/migrate/20231107072541_create_pm_links.rb
new file mode 100644
index 000000000..84cdb00e8
--- /dev/null
+++ b/db/migrate/20231107072541_create_pm_links.rb
@@ -0,0 +1,12 @@
+class CreatePmLinks < ActiveRecord::Migration[5.2]
+ def change
+ create_table :pm_links do |t|
+ t.string :be_linkable_type, null: false
+ t.integer :be_linkable_id, null: false
+
+ t.string :linkable_type, null: false, index: true
+ t.integer :linkable_id, null: false, index: true
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20231108024716_add_child_count_to_issues.rb b/db/migrate/20231108024716_add_child_count_to_issues.rb
new file mode 100644
index 000000000..73560b81e
--- /dev/null
+++ b/db/migrate/20231108024716_add_child_count_to_issues.rb
@@ -0,0 +1,5 @@
+class AddChildCountToIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_column :issues, :child_count, :integer, default:0
+ end
+end
diff --git a/db/migrate/20231114023928_add_changer_to_issues.rb b/db/migrate/20231114023928_add_changer_to_issues.rb
new file mode 100644
index 000000000..4dff74ed1
--- /dev/null
+++ b/db/migrate/20231114023928_add_changer_to_issues.rb
@@ -0,0 +1,5 @@
+class AddChangerToIssues < ActiveRecord::Migration[5.2]
+ def change
+ add_reference :issues, :changer
+ end
+end
diff --git a/db/migrate/20240104053819_add_organization_id_to_issue_tags.rb b/db/migrate/20240104053819_add_organization_id_to_issue_tags.rb
new file mode 100644
index 000000000..04cc02d0e
--- /dev/null
+++ b/db/migrate/20240104053819_add_organization_id_to_issue_tags.rb
@@ -0,0 +1,5 @@
+class AddOrganizationIdToIssueTags < ActiveRecord::Migration[5.2]
+ def change
+ add_reference :issue_tags, :organization
+ end
+end
diff --git a/db/migrate/202401111014309_add_index_uuid_to_attachments.rb b/db/migrate/202401111014309_add_index_uuid_to_attachments.rb
new file mode 100644
index 000000000..8635dbd4b
--- /dev/null
+++ b/db/migrate/202401111014309_add_index_uuid_to_attachments.rb
@@ -0,0 +1,5 @@
+class AddIndexUuidToAttachments < ActiveRecord::Migration[5.2]
+ def change
+ add_index :attachments, :uuid
+ end
+end
diff --git a/db/migrate/202401321314370_create_daily_platform_statistics.rb b/db/migrate/202401321314370_create_daily_platform_statistics.rb
new file mode 100644
index 000000000..d658979c5
--- /dev/null
+++ b/db/migrate/202401321314370_create_daily_platform_statistics.rb
@@ -0,0 +1,18 @@
+class CreateDailyPlatformStatistics < ActiveRecord::Migration[5.2]
+ def change
+ create_table :daily_platform_statistics do |t|
+ t.date :date
+ t.index :date, unique: true
+ t.bigint :pv, default: 0
+ t.bigint :visitor, default: 0
+ t.bigint :ip, default: 0
+ t.float :weekly_keep_rate, default: 0
+ t.float :source_through, default: 0
+ t.float :source_link, default: 0
+ t.float :source_search, default: 0
+ t.float :source_custom, default: 0
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/202401321314371_add_closed_issues_to_daily_project_statistics.rb b/db/migrate/202401321314371_add_closed_issues_to_daily_project_statistics.rb
new file mode 100644
index 000000000..2b38b18b0
--- /dev/null
+++ b/db/migrate/202401321314371_add_closed_issues_to_daily_project_statistics.rb
@@ -0,0 +1,5 @@
+class AddClosedIssuesToDailyProjectStatistics < ActiveRecord::Migration[5.2]
+ def change
+ add_column :daily_project_statistics, :closed_issues, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20240401030707_create_project_datasets.rb b/db/migrate/20240401030707_create_project_datasets.rb
new file mode 100644
index 000000000..7a2cfffcc
--- /dev/null
+++ b/db/migrate/20240401030707_create_project_datasets.rb
@@ -0,0 +1,11 @@
+class CreateProjectDatasets < ActiveRecord::Migration[5.2]
+ def change
+ create_table :project_datasets do |t|
+ t.string :title
+ t.text :description
+ t.references :project
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240403015938_add_license_and_paper_content_to_project_dataset.rb b/db/migrate/20240403015938_add_license_and_paper_content_to_project_dataset.rb
new file mode 100644
index 000000000..4a2a72a2d
--- /dev/null
+++ b/db/migrate/20240403015938_add_license_and_paper_content_to_project_dataset.rb
@@ -0,0 +1,6 @@
+class AddLicenseAndPaperContentToProjectDataset < ActiveRecord::Migration[5.2]
+ def change
+ add_reference :project_datasets, :license
+ add_column :project_datasets, :paper_content, :text
+ end
+end
diff --git a/db/migrate/20240408010101_create_action_node_types.rb b/db/migrate/20240408010101_create_action_node_types.rb
new file mode 100644
index 000000000..b9c530784
--- /dev/null
+++ b/db/migrate/20240408010101_create_action_node_types.rb
@@ -0,0 +1,10 @@
+class CreateActionNodeTypes < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_node_types do |t|
+ t.string :name
+ t.string :description
+ t.integer :sort_no, default: 0
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240408010102_create_action_nodes.rb b/db/migrate/20240408010102_create_action_nodes.rb
new file mode 100644
index 000000000..67331e4db
--- /dev/null
+++ b/db/migrate/20240408010102_create_action_nodes.rb
@@ -0,0 +1,18 @@
+class CreateActionNodes < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_nodes do |t|
+ t.string :name
+ t.string :full_name
+ t.string :description
+ t.string :icon
+ t.references :action_node_types
+ t.boolean :is_local, default: false
+ t.string :local_url
+ t.text :yaml
+ t.integer :sort_no, default: 0
+ t.integer :use_count, default: 0
+ t.references :user
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240408010213_create_action_node_selects.rb b/db/migrate/20240408010213_create_action_node_selects.rb
new file mode 100644
index 000000000..9d07fe4ba
--- /dev/null
+++ b/db/migrate/20240408010213_create_action_node_selects.rb
@@ -0,0 +1,17 @@
+class CreateActionNodeSelects < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_node_selects do |t|
+ t.references :action_nodes
+ t.string :name
+ t.string :val
+ t.string :val_ext
+ t.string :description
+ t.string :download_url
+ t.integer :sort_no, default: 0
+ t.integer :use_count, default: 0
+ t.references :user
+ t.timestamps
+ t.index :name, length: 191
+ end
+ end
+end
diff --git a/db/migrate/20240408010227_create_action_node_inputs.rb b/db/migrate/20240408010227_create_action_node_inputs.rb
new file mode 100644
index 000000000..a710271cd
--- /dev/null
+++ b/db/migrate/20240408010227_create_action_node_inputs.rb
@@ -0,0 +1,14 @@
+class CreateActionNodeInputs < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_node_inputs do |t|
+ t.references :action_nodes
+ t.string :name
+ t.string :input_type
+ t.string :description
+ t.boolean :is_required, default: false
+ t.integer :sort_no, default: 0
+ t.references :user
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240408010233_create_action_templates.rb b/db/migrate/20240408010233_create_action_templates.rb
new file mode 100644
index 000000000..1b4c985d2
--- /dev/null
+++ b/db/migrate/20240408010233_create_action_templates.rb
@@ -0,0 +1,13 @@
+class CreateActionTemplates < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_templates do |t|
+ t.string :name
+ t.string :description
+ t.string :img
+ t.integer :sort_no, default: 0
+ t.text :json
+ t.text :yaml
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240415014011_create_sync_repositories.rb b/db/migrate/20240415014011_create_sync_repositories.rb
new file mode 100644
index 000000000..d2908bfe9
--- /dev/null
+++ b/db/migrate/20240415014011_create_sync_repositories.rb
@@ -0,0 +1,14 @@
+class CreateSyncRepositories < ActiveRecord::Migration[5.2]
+ def change
+ create_table :sync_repositories do |t|
+ t.references :project
+ t.string :type
+ t.string :repo_name
+ t.string :external_repo_address
+ t.integer :sync_granularity
+ t.integer :sync_direction, comment: "1表示从gitlink到外部2表示从外部到gitlink"
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240415015216_create_sync_repository_branches.rb b/db/migrate/20240415015216_create_sync_repository_branches.rb
new file mode 100644
index 000000000..b44a6f396
--- /dev/null
+++ b/db/migrate/20240415015216_create_sync_repository_branches.rb
@@ -0,0 +1,14 @@
+class CreateSyncRepositoryBranches < ActiveRecord::Migration[5.2]
+ def change
+ create_table :sync_repository_branches do |t|
+ t.references :sync_repository
+ t.string :gitlink_branch_name, comment: 'gitlink分支'
+ t.string :external_branch_name, comment: '外部仓库分支'
+ t.datetime :sync_time
+ t.integer :sync_status, default: 0
+ t.integer :reposync_branch_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240417025003_add_enable_to_sync_repository_branch.rb b/db/migrate/20240417025003_add_enable_to_sync_repository_branch.rb
new file mode 100644
index 000000000..fdbd7b7ee
--- /dev/null
+++ b/db/migrate/20240417025003_add_enable_to_sync_repository_branch.rb
@@ -0,0 +1,5 @@
+class AddEnableToSyncRepositoryBranch < ActiveRecord::Migration[5.2]
+ def change
+ add_column :sync_repository_branches, :enable, :boolean, default: true
+ end
+end
diff --git a/db/migrate/20240424085125_add_external_token_to_sync_repository.rb b/db/migrate/20240424085125_add_external_token_to_sync_repository.rb
new file mode 100644
index 000000000..fde46cab0
--- /dev/null
+++ b/db/migrate/20240424085125_add_external_token_to_sync_repository.rb
@@ -0,0 +1,5 @@
+class AddExternalTokenToSyncRepository < ActiveRecord::Migration[5.2]
+ def change
+ add_column :sync_repositories, :external_token, :string
+ end
+end
diff --git a/db/migrate/20240429070012_add_webhook_gid_to_sync_repository.rb b/db/migrate/20240429070012_add_webhook_gid_to_sync_repository.rb
new file mode 100644
index 000000000..dfd3e6c72
--- /dev/null
+++ b/db/migrate/20240429070012_add_webhook_gid_to_sync_repository.rb
@@ -0,0 +1,5 @@
+class AddWebhookGidToSyncRepository < ActiveRecord::Migration[5.2]
+ def change
+ add_column :sync_repositories, :webhook_gid, :integer
+ end
+end
diff --git a/db/migrate/20240514121788_create_action_pipelines.rb b/db/migrate/20240514121788_create_action_pipelines.rb
new file mode 100644
index 000000000..b453c3943
--- /dev/null
+++ b/db/migrate/20240514121788_create_action_pipelines.rb
@@ -0,0 +1,23 @@
+class CreateActionPipelines < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_pipelines do |t|
+ t.references :project
+ t.references :user
+ t.string :pipeline_name
+ t.string :pipeline_status
+ t.string :description
+ t.string :file_name
+ t.boolean :is_graphic_design, default: false
+ t.string :repo_name
+ t.string :repo_identifier
+ t.string :repo_owner
+ t.string :branch
+ t.string :event
+ t.string :sha
+ t.text :json
+ t.text :yaml
+ t.boolean :disable, default: false
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240522015212_create_gitlink_competition_applies.rb b/db/migrate/20240522015212_create_gitlink_competition_applies.rb
new file mode 100644
index 000000000..6f24a0922
--- /dev/null
+++ b/db/migrate/20240522015212_create_gitlink_competition_applies.rb
@@ -0,0 +1,19 @@
+class CreateGitlinkCompetitionApplies < ActiveRecord::Migration[5.2]
+ def change
+ create_table :gitlink_competition_applies do |t|
+ t.integer :competition_id
+ t.string :competition_identifier
+ t.integer :team_id
+ t.string :team_name
+ t.string :school_name
+ t.string :educoder_login
+ t.string :nickname
+ t.string :phone
+ t.string :email
+ t.string :identity
+ t.string :role
+
+ t.timestamps
+ end
+ end
+end
diff --git a/lib/tasks/actions_download.rake b/lib/tasks/actions_download.rake
new file mode 100644
index 000000000..bad2d535e
--- /dev/null
+++ b/lib/tasks/actions_download.rake
@@ -0,0 +1,82 @@
+# actions 下载包
+# node go java
+namespace :actions_download do
+
+ task go: :environment do
+ # curl -X GET --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/mindspore/mindspore/issues?access_token=5ccebd935915fb6cfcae634b161047a2&state=open&sort=created&direction=desc&page=1&per_page=10'
+ # api_url = "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json"
+ api_url = "https://testgitea2.trustie.net/actions/go-versions/raw/branch/main/versions-manifest.json"
+ uri = URI.parse(api_url)
+ response = Net::HTTP.get_response(uri)
+ puts "gitee api response.code ===== #{response.code}"
+ lists = JSON.parse(response.body)
+ puts "lists.size =====#{lists.size}"
+ lists.each do |data|
+ version_arr = data['version'].to_s.split(".")
+ if version_arr[0].to_i == 1 && version_arr[1].to_i >= 18
+ action_node_select = Action::NodeSelect.find_or_initialize_by(name: "go-version", val: data["version"])
+ puts data["version"]
+ data['files'].each do |file|
+ if file['platform'] == "linux"
+ puts "download_url==#{file['download_url']}"
+ action_node_select.download_url = file['download_url']
+ end
+ end
+ action_node_select.action_nodes_id=1
+ action_node_select.save
+ end
+ end
+ end
+
+ task node: :environment do
+ # curl -X GET --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/mindspore/mindspore/issues?access_token=5ccebd935915fb6cfcae634b161047a2&state=open&sort=created&direction=desc&page=1&per_page=10'
+ # api_url = "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json"
+ api_url = "https://testgitea2.trustie.net/actions/node-versions/raw/branch/main/versions-manifest.json"
+ uri = URI.parse(api_url)
+ response = Net::HTTP.get_response(uri)
+ puts "gitee api response.code ===== #{response.code}"
+ lists = JSON.parse(response.body)
+ puts "lists.size =====#{lists.size}"
+ lists.each do |data|
+ version_arr = data['version'].to_s.split(".")
+ if version_arr[0].to_i >= 16
+ puts data["version"]
+ action_node_select = Action::NodeSelect.find_or_initialize_by(name: "node-version", val: data["version"])
+ data['files'].each do |file|
+ if file['platform'] == "linux"
+ puts "download_url==#{file['download_url']}"
+ action_node_select.download_url = file['download_url']
+ end
+ end
+ action_node_select.action_nodes_id=2
+ action_node_select.save
+ end
+ end
+ end
+
+ task java: :environment do
+ # curl -X GET --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/repos/mindspore/mindspore/issues?access_token=5ccebd935915fb6cfcae634b161047a2&state=open&sort=created&direction=desc&page=1&per_page=10'
+ # api_url = "https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json"
+ [0, 1, 2].each do |page|
+ api_url = "https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?project=jdk&vendor=adoptium&heap_size=normal&sort_method=DEFAULT&sort_order=DESC&os=linux&architecture=x64&image_type=jdk&release_type=ga&jvm_impl=hotspot&page_size=20&page=#{page}"
+ uri = URI.parse(api_url)
+ response = Net::HTTP.get_response(uri)
+ puts "gitee api response.code ===== #{response.code}"
+ lists = JSON.parse(response.body)
+ puts "lists.size =====#{lists.size}"
+ lists.each do |data|
+ puts data["release_name"]
+ puts "#{data['version_data']['major']}@#{data['version_data']['openjdk_version']}"
+ action_node_select = Action::NodeSelect.find_or_initialize_by(name: "java-version", val: "#{data['version_data']['major']}", val_ext: "#{data['version_data']['openjdk_version']}")
+ data['binaries'].each do |file|
+ puts "download_url==#{file['package']['link']}"
+ action_node_select.download_url = file['package']['link']
+ end
+ action_node_select.action_nodes_id=5
+ action_node_select.save
+ end
+ end
+
+ end
+
+end
\ No newline at end of file
diff --git a/lib/tasks/import_issues_from_chandao.rake b/lib/tasks/import_issues_from_chandao.rake
new file mode 100644
index 000000000..41bbc3356
--- /dev/null
+++ b/lib/tasks/import_issues_from_chandao.rake
@@ -0,0 +1,106 @@
+
+desc "导入禅道数据"
+namespace :import_from_chandao do
+ desc "bug数据"
+ # 执行示例 bundle exec rake "import_from_chandao:bugs[企业内部工时管理系统.csv, 3]"
+ # RAILS_ENV=production bundle exec rake "import_from_chandao:bugs[企业内部工时管理系统.csv, 3]"
+ task :bugs, [:name, :pm_project_id] => :environment do |t, args|
+ name = args.name
+ CSV.foreach("#{Rails.root}/#{args.name}", headers: true) do | row |
+ randd_field_hash = row.to_hash
+ issue = Issue.new(issue_classify: "issue")
+ author = User.like(randd_field_hash['由谁创建']).take
+ issue.author_id = author&.id
+ assigner = randd_field_hash['指派给'].present? ? User.like(randd_field_hash['指派给']).take : nil
+ if assigner.present?
+ issue.assigners << assigner
+ end
+ issue.status_id = IssueStatus.first.id
+ issue.tracker_id = Tracker.first.id
+ issue.priority_id = randd_field_hash['优先级'].to_i
+ issue.subject = randd_field_hash['Bug标题']
+ issue.description = randd_field_hash['重现步骤']
+ issue.created_on = randd_field_hash['创建日期'].to_time
+ issue.due_date = randd_field_hash['截止日期']
+ issue.project_id = 0
+ issue.pm_project_id = args.pm_project_id
+ issue.pm_issue_type = 3
+ issue.save!
+ end
+ end
+
+ # 执行示例 bundle exec rake "import_from_chandao:tasks[复杂智能软件项目-所有任务.csv, 365]"
+ # RAILS_ENV=production bundle exec rake "import_from_chandao:tasks[复杂智能软件项目-所有任务.csv, 365]"
+ task :tasks, [:name, :pm_project_id] => :environment do |t, args|
+ def trans_status(str)
+ h={
+ "未开始" => 1,
+ "进行中" => 2,
+ "已完成" => 3,
+ "已关闭" => 5
+ }
+ h[str]
+ end
+
+ name = args.name
+ pm_project_id = args.pm_project_id
+ CSV.foreach("#{Rails.root}/#{name}", headers: true) do | row |
+ randd_field_hash = row.to_hash
+ issue = Issue.new(issue_classify: "issue")
+ author = User.like(randd_field_hash['由谁创建']).take
+ issue.author_id = author&.id
+ assigner = randd_field_hash['指派给'].present? ? User.like(randd_field_hash['指派给']).take : nil
+ if assigner.present?
+ issue.assigners << assigner
+ end
+ issue.status_id = trans_status(randd_field_hash['任务状态']) || IssueStatus.first.id
+ issue.tracker_id = Tracker.first.id
+ issue.priority_id = randd_field_hash['优先级'].to_i
+ issue.subject = randd_field_hash['任务名称']
+ issue.description = randd_field_hash['任务描述']
+ issue.created_on = randd_field_hash['创建日期'].to_time rescue nil
+ issue.updated_on = randd_field_hash['最后修改日期'].to_time rescue issue.created_on
+ issue.time_scale = randd_field_hash['最初预计'].to_i
+ issue.start_date = randd_field_hash['预计开始'].to_time rescue nil
+ issue.due_date = randd_field_hash['截止日期'].to_time rescue nil
+ issue.project_id = 0
+ issue.pm_project_id = pm_project_id
+ issue.pm_issue_type = 2
+ issue.save!
+ requirement_issue = Issue.find_by(project_issues_index: randd_field_hash['相关需求'].split('(#')[1].split(')')[0], pm_project_id: pm_project_id, pm_issue_type: 1) rescue nil
+ if requirement_issue.present?
+ requirement_issue.pm_links.find_or_create_by(be_linkable_type: 'Issue', be_linkable_id: issue.id)
+ end
+ end
+ end
+
+ # 执行示例 bundle exec rake "import_from_chandao:requirements[企业网站第二期.csv, 3]"
+ # RAILS_ENV=production bundle exec rake "import_from_chandao:requirements[企业网站第二期.csv, 3]"
+ task :requirements, [:name, :pm_project_id] => :environment do |t, args|
+ name = args.name
+ pm_project_id = args.pm_project_id
+ CSV.foreach("#{Rails.root}/#{name}", headers: true) do | row |
+ randd_field_hash = row.to_hash
+ issue = Issue.new(issue_classify: "issue")
+ author = User.like(randd_field_hash['由谁创建']).take
+ issue.author_id = author&.id
+ assigner = randd_field_hash['指派给'].present? ? User.like(randd_field_hash['指派给']).take : nil
+ if assigner.present?
+ issue.assigners << assigner
+ end
+ issue.project_issues_index = randd_field_hash['编号'].to_i
+ issue.status_id = IssueStatus.first.id
+ issue.tracker_id = Tracker.first.id
+ issue.priority_id = randd_field_hash['优先级'].to_i
+ issue.subject = randd_field_hash['需求名称']
+ issue.description = randd_field_hash['需求描述']
+ issue.created_on = randd_field_hash['创建日期'].to_time
+ issue.updated_on = randd_field_hash['最后修改日期'].to_time rescue randd_field_hash['创建日期'].to_time
+ issue.time_scale = randd_field_hash['预计工时'].to_i
+ issue.project_id = 0
+ issue.pm_project_id = pm_project_id
+ issue.pm_issue_type = 1
+ issue.save!
+ end
+ end
+end
\ No newline at end of file
diff --git a/public/MP_verify_lvSp9mqhewuv8zRT.txt b/public/MP_verify_lvSp9mqhewuv8zRT.txt
new file mode 100644
index 000000000..e23f48cc0
--- /dev/null
+++ b/public/MP_verify_lvSp9mqhewuv8zRT.txt
@@ -0,0 +1 @@
+lvSp9mqhewuv8zRT
\ No newline at end of file
diff --git a/public/editormd/lib/marked.min.js b/public/editormd/lib/marked.min.js
index eb14b2545..9fdbc8481 100644
--- a/public/editormd/lib/marked.min.js
+++ b/public/editormd/lib/marked.min.js
@@ -1,22 +1,6 @@
/**
- * marked v0.3.3 - a markdown parser
- * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
- * https://github.com/chjj/marked
-
- 备注,这个改动没启用,只是做个记录:
- br不转成br
- 加了个 if (cap[0] != '' && cap[0] != '
') { out+=this.options.sanitize?escape(cap[0]):cap[0]; }
-
- out+=this.renderer.em(this.output(cap[2]||cap[1])) -->
- out+=this.renderer.em(this.output(cap[2]||cap[1]), cap.input)
+ * marked - a markdown parser
+ * Copyright (c) 2011-2021, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/markedjs/marked
*/
-// 0.4.0 /^ *(#{1,6}) ——》/^ *(#{1,6}) 去掉了一个空格
-/*if("string"!=typeof e)throw new Error("marked(): input parameter is of type "+Object.prototype.toString.call(e)+", string expected");*/
-// 说明:左边 --> 右边 左边被替换成了右边的内容
-// b(i[1].replace(/^ *| *\| *$/g,"")) --> i[1].replace(/^ *| *\| *$/g, "").split(/ *\| */) table没识别的问题
-// header.length===a.align.length --> header.length table没识别的问题
-// 2个table: b(a.cells[p],a.header.length) -> a.cells[p].replace(/^ *\| *| *\| *$/g, "").split(/ *\| */)
-// .replace(/(?: *\| *)?\n$/,"") --> .replace(/\n$/, "")
-// /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ --> /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
-!function(e){"use strict";var t={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:d,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ *(#{1,6})*([^\n]+?) *(?:#+ *)?(?:\n+|$)/,nptable:d,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)|(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,table:d,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,text:/^[^\n]+/};function n(e){this.tokens=[],this.tokens.links={},this.options=e||m.defaults,this.rules=t.normal,this.options.pedantic?this.rules=t.pedantic:this.options.gfm&&(this.options.tables?this.rules=t.tables:this.rules=t.gfm)}t._label=/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,t._title=/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/,t.def=p(t.def).replace("label",t._label).replace("title",t._title).getRegex(),t.bullet=/(?:[*+-]|\d+\.)/,t.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,t.item=p(t.item,"gm").replace(/bull/g,t.bullet).getRegex(),t.list=p(t.list).replace(/bull/g,t.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+t.def.source+")").getRegex(),t._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",t._comment=//,t.html=p(t.html,"i").replace("comment",t._comment).replace("tag",t._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),t.paragraph=p(t.paragraph).replace("hr",t.hr).replace("heading",t.heading).replace("lheading",t.lheading).replace("tag",t._tag).getRegex(),t.blockquote=p(t.blockquote).replace("paragraph",t.paragraph).getRegex(),t.normal=f({},t),t.gfm=f({},t.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6})+([^\n]+?) *#* *(?:\n+|$)/}),t.gfm.paragraph=p(t.paragraph).replace("(?!","(?!"+t.gfm.fences.source.replace("\\1","\\2")+"|"+t.list.source.replace("\\1","\\3")+"|").getRegex(),t.tables=f({},t.gfm,{nptable:/^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),t.pedantic=f({},t.normal,{html:p("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)|
\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",t._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/}),n.rules=t,n.lex=function(e,t){return new n(t).lex(e)},n.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},n.prototype.token=function(e,n){var r,s,i,l,o,a,h,p,u,c,g,d,f;for(e=e.replace(/^ +$/gm,"");e;)if((i=this.rules.newline.exec(e))&&(e=e.substring(i[0].length),i[0].length>1&&this.tokens.push({type:"space"})),i=this.rules.code.exec(e))e=e.substring(i[0].length),i=i[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?i:i.replace(/\n+$/,"")});else if(i=this.rules.fences.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"code",lang:i[2],text:i[3]||""});else if(i=this.rules.heading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:i[1].length,text:i[2]});else if(n&&(i=this.rules.nptable.exec(e))&&(a={type:"table",header:i[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3]?i[3].replace(/\n$/,"").split("\n"):[]}).header.length){for(e=e.substring(i[0].length),p=0;p
?/gm,""),this.token(i,n),this.tokens.push({type:"blockquote_end"});else if(i=this.rules.list.exec(e)){for(e=e.substring(i[0].length),g=(l=i[2]).length>1,this.tokens.push({type:"list_start",ordered:g,start:g?+l:""}),r=!1,c=(i=i[0].match(this.rules.item)).length,p=0;p1&&o.length>1||(e=i.slice(p+1).join("\n")+e,p=c-1)),s=r||/\n\n(?!\s*$)/.test(a),p!==c-1&&(r="\n"===a.charAt(a.length-1),s||(s=r)),f=void 0,(d=/^\[[ xX]\] /.test(a))&&(f=" "!==a[1],a=a.replace(/^\[[ xX]\] +/,"")),this.tokens.push({type:s?"loose_item_start":"list_item_start",task:d,checked:f}),this.token(a,!1),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(i=this.rules.html.exec(e))e=e.substring(i[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&("pre"===i[1]||"script"===i[1]||"style"===i[1]),text:i[0]});else if(n&&(i=this.rules.def.exec(e)))e=e.substring(i[0].length),i[3]&&(i[3]=i[3].substring(1,i[3].length-1)),u=i[1].toLowerCase().replace(/\s+/g," "),this.tokens.links[u]||(this.tokens.links[u]={href:i[2],title:i[3]});else if(n&&(i=this.rules.table.exec(e))&&(a={type:"table",header:i[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3]?i[3].replace(/\n$/, "").split("\n"):[]}).header.length){for(e=e.substring(i[0].length),p=0;p?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:d,tag:"^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,strong:/^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/,em:/^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:d,text:/^[\s\S]+?(?=[\\/g,">").replace(/"/g,""").replace(/'/g,"'")}function h(e){return e.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function p(e,t){return e=e.source||e,t=t||"",{replace:function(t,n){return n=(n=n.source||n).replace(/(^|[^\[])\^/g,"$1"),e=e.replace(t,n),this},getRegex:function(){return new RegExp(e,t)}}}function u(e,t){return c[" "+e]||(/^[^:]+:\/*[^/]*$/.test(e)?c[" "+e]=e+"/":c[" "+e]=e.replace(/[^/]*$/,"")),e=c[" "+e],"//"===t.slice(0,2)?e.replace(/:[\s\S]*/,":")+t:"/"===t.charAt(0)?e.replace(/(:\/*[^/]*)[\s\S]*/,"$1")+t:e+t}r._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,r._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,r._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,r.autolink=p(r.autolink).replace("scheme",r._scheme).replace("email",r._email).getRegex(),r._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,r.tag=p(r.tag).replace("comment",t._comment).replace("attribute",r._attribute).getRegex(),r._label=/(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/,r._href=/\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/,r._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,r.link=p(r.link).replace("label",r._label).replace("href",r._href).replace("title",r._title).getRegex(),r.reflink=p(r.reflink).replace("label",r._label).getRegex(),r.normal=f({},r),r.pedantic=f({},r.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,link:p(/^!?\[(label)\]\((.*?)\)/).replace("label",r._label).getRegex(),reflink:p(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",r._label).getRegex()}),r.gfm=f({},r.normal,{escape:p(r.escape).replace("])","~|])").getRegex(),url:p(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("email",r._email).getRegex(),_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:p(r.text).replace("]|","~]|").replace("|","|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&'*+/=?^_`{\\|}~-]+@|").getRegex()}),r.breaks=f({},r.gfm,{br:p(r.br).replace("{2,}","*").getRegex(),text:p(r.gfm.text).replace("{2,}","*").getRegex()}),s.rules=r,s.output=function(e,t,n){return new s(t,n).output(e)},s.prototype.output=function(e){for(var t,n,r,i,l,o="";e;)if(l=this.rules.escape.exec(e))e=e.substring(l[0].length),o+=l[1];else if(l=this.rules.autolink.exec(e))e=e.substring(l[0].length),r="@"===l[2]?"mailto:"+(n=a(this.mangle(l[1]))):n=a(l[1]),o+=this.renderer.link(r,null,n);else if(this.inLink||!(l=this.rules.url.exec(e))){if(l=this.rules.tag.exec(e))!this.inLink&&/^/i.test(l[0])&&(this.inLink=!1),e=e.substring(l[0].length),o+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(l[0]):a(l[0]):l[0];else if(l=this.rules.link.exec(e))e=e.substring(l[0].length),this.inLink=!0,r=l[2],this.options.pedantic?(t=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(r))?(r=t[1],i=t[3]):i="":i=l[3]?l[3].slice(1,-1):"",r=r.trim().replace(/^<([\s\S]*)>$/,"$1"),o+=this.outputLink(l,{href:s.escapes(r),title:s.escapes(i)}),this.inLink=!1;else if((l=this.rules.reflink.exec(e))||(l=this.rules.nolink.exec(e))){if(e=e.substring(l[0].length),t=(l[2]||l[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){o+=l[0].charAt(0),e=l[0].substring(1)+e;continue}this.inLink=!0,o+=this.outputLink(l,t),this.inLink=!1}else if(l=this.rules.strong.exec(e))e=e.substring(l[0].length),o+=this.renderer.strong(this.output(l[4]||l[3]||l[2]||l[1]));else if(l=this.rules.em.exec(e))e=e.substring(l[0].length),o+=this.renderer.em(this.output(l[6]||l[5]||l[4]||l[3]||l[2]||l[1]));else if(l=this.rules.code.exec(e))e=e.substring(l[0].length),o+=this.renderer.codespan(a(l[2].trim(),!0));else if(l=this.rules.br.exec(e))e=e.substring(l[0].length),o+=this.renderer.br();else if(l=this.rules.del.exec(e))e=e.substring(l[0].length),o+=this.renderer.del(this.output(l[1]));else if(l=this.rules.text.exec(e))e=e.substring(l[0].length),o+=this.renderer.text(a(this.smartypants(l[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else l[0]=this.rules._backpedal.exec(l[0])[0],e=e.substring(l[0].length),"@"===l[2]?r="mailto:"+(n=a(l[0])):(n=a(l[0]),r="www."===l[1]?"http://"+n:n),o+=this.renderer.link(r,null,n);return o},s.escapes=function(e){return e?e.replace(s.rules._escapes,"$1"):e},s.prototype.outputLink=function(e,t){var n=t.href,r=t.title?a(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,a(e[1]))},s.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},s.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,s=0;s.5&&(t="x"+t.toString(16)),n+=""+t+";";return n},i.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?''+(n?e:a(e,!0))+" \n":""+(n?e:a(e,!0))+" "},i.prototype.blockquote=function(e){return"\n"+e+" \n"},i.prototype.html=function(e){return e},i.prototype.heading=function(e,t,n){return this.options.headerIds?"\n":""+e+" \n"},i.prototype.hr=function(){return this.options.xhtml?" \n":" \n"},i.prototype.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+""+r+">\n"},i.prototype.listitem=function(e){return""+e+" \n"},i.prototype.checkbox=function(e){return" "},i.prototype.paragraph=function(e){return""+e+"
\n"},i.prototype.table=function(e,t){return t&&(t=""+t+" "),"\n"},i.prototype.tablerow=function(e){return"\n"+e+" \n"},i.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+""+n+">\n"},i.prototype.strong=function(e){return""+e+" "},i.prototype.em=function(e){return""+e+" "},i.prototype.codespan=function(e){return""+e+""},i.prototype.br=function(){return this.options.xhtml?" ":" "},i.prototype.del=function(e){return""+e+""},i.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent(h(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return n}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:")||0===r.indexOf("data:"))return n}this.options.baseUrl&&!g.test(e)&&(e=u(this.options.baseUrl,e));try{e=encodeURI(e).replace(/%25/g,"%")}catch(e){return n}var s='"+n+" "},i.prototype.image=function(e,t,n){this.options.baseUrl&&!g.test(e)&&(e=u(this.options.baseUrl,e));var r=' ":">"},i.prototype.text=function(e){return e},l.prototype.strong=l.prototype.em=l.prototype.codespan=l.prototype.del=l.prototype.text=function(e){return e},l.prototype.link=l.prototype.image=function(e,t,n){return""+n},l.prototype.br=function(){return""},o.parse=function(e,t){return new o(t).parse(e)},o.prototype.parse=function(e){this.inline=new s(e.links,this.options),this.inlineText=new s(e.links,f({},this.options,{renderer:new l})),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},o.prototype.next=function(){return this.token=this.tokens.pop()},o.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},o.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},o.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,h(this.inlineText.output(this.token.text)));case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,s="",i="";for(n="",e=0;et)n.splice(t);else for(;n.lengthAn error occurred:"+a(e.message+"",!0)+" ";throw e}}d.exec=d,m.options=m.setOptions=function(e){return f(m.defaults,e),m},m.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new i,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tables:!0,xhtml:!1}},m.defaults=m.getDefaults(),m.Parser=o,m.parser=o.parse,m.Renderer=i,m.TextRenderer=l,m.Lexer=n,m.lexer=n.lex,m.InlineLexer=s,m.inlineLexer=s.output,m.parse=m,"undefined"!=typeof module&&"object"==typeof exports?module.exports=m:"function"==typeof define&&define.amd?define(function(){return m}):e.marked=m}(this||("undefined"!=typeof window?window:global));
-
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).marked=t()}(this,function(){"use strict";function r(e,t){for(var u=0;ue.length)&&(t=e.length);for(var u=0,n=new Array(t);u=e.length?{done:!0}:{done:!1,value:e[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var t={exports:{}};function e(){return{baseUrl:null,breaks:!1,extensions:null,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}t.exports={defaults:e(),getDefaults:e,changeDefaults:function(e){t.exports.defaults=e}};function u(e){return D[e]}var n=/[&<>"']/,s=/[&<>"']/g,l=/[<>"']|&(?!#?\w+;)/,a=/[<>"']|&(?!#?\w+;)/g,D={"&":"&","<":"<",">":">",'"':""","'":"'"};var c=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function h(e){return e.replace(c,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var p=/(^|[^\[])\^/g;var g=/[^\w:]/g,f=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var F={},A=/^[^:]+:\/*[^/]*$/,d=/^([^:]+:)[\s\S]*$/,C=/^([^:]+:\/*[^/]*)[\s\S]*$/;function k(e,t){F[" "+e]||(A.test(e)?F[" "+e]=e+"/":F[" "+e]=E(e,"/",!0));var u=-1===(e=F[" "+e]).indexOf(":");return"//"===t.substring(0,2)?u?t:e.replace(d,"$1")+t:"/"===t.charAt(0)?u?t:e.replace(C,"$1")+t:e+t}function E(e,t,u){var n=e.length;if(0===n)return"";for(var r=0;rt)u.splice(t);else for(;u.length>=1,e+=e;return u+e},T=t.exports.defaults,I=_,R=y,q=x,Z=z;function O(e,t,u){var n=t.href,r=t.title?q(t.title):null,t=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?{type:"link",raw:u,href:n,title:r,text:t}:{type:"image",raw:u,href:n,title:r,text:q(t)}}_=function(){function e(e){this.options=e||T}var t=e.prototype;return t.space=function(e){e=this.rules.block.newline.exec(e);if(e)return 1=u.length?e.slice(u.length):e}).join("\n")}(u,t[3]||"");return{type:"code",raw:u,lang:t[2]&&t[2].trim(),text:e}}},t.heading=function(e){var t=this.rules.block.heading.exec(e);if(t){var u=t[2].trim();return/#$/.test(u)&&(e=I(u,"#"),!this.options.pedantic&&e&&!/ $/.test(e)||(u=e.trim())),{type:"heading",raw:t[0],depth:t[1].length,text:u}}},t.nptable=function(e){e=this.rules.block.nptable.exec(e);if(e){var t={type:"table",header:R(e[1].replace(/^ *| *\| *$/g,"")),align:e[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:e[3]?e[3].replace(/\n$/,"").split("\n"):[],raw:e[0]};if(t.header.length===t.align.length){for(var u=t.align.length,n=0;n ?/gm,"");return{type:"blockquote",raw:t[0],text:e}}},t.list=function(e){e=this.rules.block.list.exec(e);if(e){for(var t,u,n,r,i,s,l=e[0],a=e[2],o=1g[1].length:n[1].length>=g[0].length||3/i.test(e[0])&&(t=!1),!u&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?u=!0:u&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(u=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:t,inRawBlock:u,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):q(e[0]):e[0]}},t.link=function(e){var t=this.rules.inline.link.exec(e);if(t){var u=t[2].trim();if(!this.options.pedantic&&/^$/.test(u))return;e=I(u.slice(0,-1),"\\");if((u.length-e.length)%2==0)return}else{var n=Z(t[2],"()");-1 $/.test(u)?n.slice(1):n.slice(1,-1):n)&&n.replace(this.rules.inline._escapes,"$1"),title:i&&i.replace(this.rules.inline._escapes,"$1")},t[0])}},t.reflink=function(e,t){if((u=this.rules.inline.reflink.exec(e))||(u=this.rules.inline.nolink.exec(e))){e=(u[2]||u[1]).replace(/\s+/g," ");if((e=t[e.toLowerCase()])&&e.href)return O(u,e,u[0]);var u=u[0].charAt(0);return{type:"text",raw:u,text:u}}},t.emStrong=function(e,t,u){void 0===u&&(u="");var n=this.rules.inline.emStrong.lDelim.exec(e);if(n&&(!n[3]||!u.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/))){var r=n[1]||n[2]||"";if(!r||r&&(""===u||this.rules.inline.punctuation.exec(u))){var i,s=n[0].length-1,l=s,a=0,o="*"===n[0][0]?this.rules.inline.emStrong.rDelimAst:this.rules.inline.emStrong.rDelimUnd;for(o.lastIndex=0,t=t.slice(-1*e.length+s);null!=(n=o.exec(t));)if(i=n[1]||n[2]||n[3]||n[4]||n[5]||n[6])if(i=i.length,n[3]||n[4])l+=i;else if(!((n[5]||n[6])&&s%3)||(s+i)%3){if(!(0<(l-=i)))return i=Math.min(i,i+l+a),Math.min(s,i)%2?{type:"em",raw:e.slice(0,s+n.index+i+1),text:e.slice(1,s+n.index+i)}:{type:"strong",raw:e.slice(0,s+n.index+i+1),text:e.slice(2,s+n.index+i-1)}}else a+=i}}},t.codespan=function(e){var t=this.rules.inline.code.exec(e);if(t){var u=t[2].replace(/\n/g," "),n=/[^ ]/.test(u),e=/^ /.test(u)&&/ $/.test(u);return n&&e&&(u=u.substring(1,u.length-1)),u=q(u,!0),{type:"codespan",raw:t[0],text:u}}},t.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},t.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2]}},t.autolink=function(e,t){e=this.rules.inline.autolink.exec(e);if(e){var u,t="@"===e[2]?"mailto:"+(u=q(this.options.mangle?t(e[1]):e[1])):u=q(e[1]);return{type:"link",raw:e[0],text:u,href:t,tokens:[{type:"text",raw:u,text:u}]}}},t.url=function(e,t){var u,n,r,i;if(u=this.rules.inline.url.exec(e)){if("@"===u[2])r="mailto:"+(n=q(this.options.mangle?t(u[0]):u[0]));else{for(;i=u[0],u[0]=this.rules.inline._backpedal.exec(u[0])[0],i!==u[0];);n=q(u[0]),r="www."===u[1]?"http://"+n:n}return{type:"link",raw:u[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}},t.inlineText=function(e,t,u){e=this.rules.inline.text.exec(e);if(e){u=t?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):q(e[0]):e[0]:q(this.options.smartypants?u(e[0]):e[0]);return{type:"text",raw:e[0],text:u}}},e}(),y=w,z=b,w=v,b={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))",def:/^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:y,table:y,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};b.def=z(b.def).replace("label",b._label).replace("title",b._title).getRegex(),b.bullet=/(?:[*+-]|\d{1,9}[.)])/,b.item=/^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/,b.item=z(b.item,"gm").replace(/bull/g,b.bullet).getRegex(),b.listItemStart=z(/^( *)(bull) */).replace("bull",b.bullet).getRegex(),b.list=z(b.list).replace(/bull/g,b.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+b.def.source+")").getRegex(),b._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",b._comment=/|$)/,b.html=z(b.html,"i").replace("comment",b._comment).replace("tag",b._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),b.paragraph=z(b._paragraph).replace("hr",b.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",b._tag).getRegex(),b.blockquote=z(b.blockquote).replace("paragraph",b.paragraph).getRegex(),b.normal=w({},b),b.gfm=w({},b.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n {0,3}([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n {0,3}\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),b.gfm.nptable=z(b.gfm.nptable).replace("hr",b.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",b._tag).getRegex(),b.gfm.table=z(b.gfm.table).replace("hr",b.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",b._tag).getRegex(),b.pedantic=w({},b.normal,{html:z("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)| \\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",b._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:y,paragraph:z(b.normal._paragraph).replace("hr",b.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",b.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});y={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:y,tag:"^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",emStrong:{lDelim:/^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,rDelimAst:/\_\_[^_*]*?\*[^_*]*?\_\_|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,rDelimUnd:/\*\*[^_*]*?\_[^_*]*?\*\*|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:y,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~"};y.punctuation=z(y.punctuation).replace(/punctuation/g,y._punctuation).getRegex(),y.blockSkip=/\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g,y.escapedEmSt=/\\\*|\\_/g,y._comment=z(b._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),y.emStrong.lDelim=z(y.emStrong.lDelim).replace(/punct/g,y._punctuation).getRegex(),y.emStrong.rDelimAst=z(y.emStrong.rDelimAst,"g").replace(/punct/g,y._punctuation).getRegex(),y.emStrong.rDelimUnd=z(y.emStrong.rDelimUnd,"g").replace(/punct/g,y._punctuation).getRegex(),y._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,y._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,y._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,y.autolink=z(y.autolink).replace("scheme",y._scheme).replace("email",y._email).getRegex(),y._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,y.tag=z(y.tag).replace("comment",y._comment).replace("attribute",y._attribute).getRegex(),y._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,y._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,y._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,y.link=z(y.link).replace("label",y._label).replace("href",y._href).replace("title",y._title).getRegex(),y.reflink=z(y.reflink).replace("label",y._label).getRegex(),y.reflinkSearch=z(y.reflinkSearch,"g").replace("reflink",y.reflink).replace("nolink",y.nolink).getRegex(),y.normal=w({},y),y.pedantic=w({},y.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:z(/^!?\[(label)\]\((.*?)\)/).replace("label",y._label).getRegex(),reflink:z(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",y._label).getRegex()}),y.gfm=w({},y.normal,{escape:z(y.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\'+(u?e:H(e,!0))+"\n":""+(u?e:H(e,!0))+" \n"},t.blockquote=function(e){return"\n"+e+" \n"},t.html=function(e){return e},t.heading=function(e,t,u,n){return this.options.headerIds?"\n":""+e+" \n"},t.hr=function(){return this.options.xhtml?" \n":" \n"},t.list=function(e,t,u){var n=t?"ol":"ul";return"<"+n+(t&&1!==u?' start="'+u+'"':"")+">\n"+e+""+n+">\n"},t.listitem=function(e){return""+e+" \n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return""+e+"
\n"},t.table=function(e,t){return"\n\n"+e+" \n"+(t=t&&""+t+" ")+"
\n"},t.tablerow=function(e){return"\n"+e+" \n"},t.tablecell=function(e,t){var u=t.header?"th":"td";return(t.align?"<"+u+' align="'+t.align+'">':"<"+u+">")+e+""+u+">\n"},t.strong=function(e){return""+e+" "},t.em=function(e){return""+e+" "},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?" ":" "},t.del=function(e){return""+e+""},t.link=function(e,t,u){if(null===(e=V(this.options.sanitize,this.options.baseUrl,e)))return u;e='"+u+" "},t.image=function(e,t,u){if(null===(e=V(this.options.sanitize,this.options.baseUrl,e)))return u;u=' ":">"},t.text=function(e){return e},e}(),S=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,u){return""+u},t.image=function(e,t,u){return""+u},t.br=function(){return""},e}(),B=function(){function e(){this.seen={}}var t=e.prototype;return t.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},t.getNextSafeSlug=function(e,t){var u=e,n=0;if(this.seen.hasOwnProperty(u))for(n=this.seen[e];u=e+"-"+ ++n,this.seen.hasOwnProperty(u););return t||(this.seen[e]=n,this.seen[u]=0),u},t.slug=function(e,t){void 0===t&&(t={});var u=this.serialize(e);return this.getNextSafeSlug(u,t.dryrun)},e}(),J=b,K=S,Q=B,W=t.exports.defaults,Y=m,ee=y,te=function(){function u(e){this.options=e||W,this.options.renderer=this.options.renderer||new J,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new K,this.slugger=new Q}u.parse=function(e,t){return new u(t).parse(e)},u.parseInline=function(e,t){return new u(t).parseInline(e)};var e=u.prototype;return e.parse=function(e,t){void 0===t&&(t=!0);for(var u,n,r,i,s,l,a,o,D,c,h,p,g,f,F,A,d="",C=e.length,k=0;kAn error occurred:"+se(e.message+"",!0)+" ";throw e}}return ae.options=ae.setOptions=function(e){return re(ae.defaults,e),le(ae.defaults),ae},ae.getDefaults=$,ae.defaults=x,ae.use=function(){for(var u=this,e=arguments.length,t=new Array(e),n=0;nAn error occurred:"+se(e.message+"",!0)+" ";throw e}},ae.Parser=te,ae.parser=te.parse,ae.Renderer=ne,ae.TextRenderer=S,ae.Lexer=ee,ae.lexer=ee.lex,ae.Tokenizer=ue,ae.Slugger=B,ae.parse=ae});
\ No newline at end of file
diff --git a/public/操作系统大赛用户信息.csv b/public/操作系统大赛用户信息.csv
new file mode 100644
index 000000000..7365072b2
--- /dev/null
+++ b/public/操作系统大赛用户信息.csv
@@ -0,0 +1,2 @@
+用户名,邮箱,密码,手机号,昵称
+123456789,yystopf1@163.com,9b653a7d,15386415122,何慧
diff --git a/spec/controllers/api/pm/issue_links_controller_spec.rb b/spec/controllers/api/pm/issue_links_controller_spec.rb
new file mode 100644
index 000000000..a80df2c77
--- /dev/null
+++ b/spec/controllers/api/pm/issue_links_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::Pm::IssueLinksController, type: :controller do
+
+end
diff --git a/spec/controllers/api/pm/projects_controller_spec.rb b/spec/controllers/api/pm/projects_controller_spec.rb
new file mode 100644
index 000000000..8b9e3c903
--- /dev/null
+++ b/spec/controllers/api/pm/projects_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::Pm::ProjectsController, type: :controller do
+
+end
diff --git a/spec/controllers/api/v1/pm_issues_controller_spec.rb b/spec/controllers/api/v1/pm_issues_controller_spec.rb
new file mode 100644
index 000000000..f68abf45f
--- /dev/null
+++ b/spec/controllers/api/v1/pm_issues_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::PmIssuesController, type: :controller do
+
+end
diff --git a/spec/controllers/api/v1/sonarqubes_controller_spec.rb b/spec/controllers/api/v1/sonarqubes_controller_spec.rb
new file mode 100644
index 000000000..ce71590c4
--- /dev/null
+++ b/spec/controllers/api/v1/sonarqubes_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::SonarqubesController, type: :controller do
+
+end
diff --git a/spec/controllers/pm/journals_controller_spec.rb b/spec/controllers/pm/journals_controller_spec.rb
new file mode 100644
index 000000000..0a0ced0ba
--- /dev/null
+++ b/spec/controllers/pm/journals_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Pm::JournalsController, type: :controller do
+
+end
diff --git a/spec/helpers/api/pm/issue_links_helper_spec.rb b/spec/helpers/api/pm/issue_links_helper_spec.rb
new file mode 100644
index 000000000..924962a6c
--- /dev/null
+++ b/spec/helpers/api/pm/issue_links_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::Pm::IssueLinksHelper. For example:
+#
+# describe Api::Pm::IssueLinksHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::Pm::IssueLinksHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/api/pm/projects_helper_spec.rb b/spec/helpers/api/pm/projects_helper_spec.rb
new file mode 100644
index 000000000..b93449cc2
--- /dev/null
+++ b/spec/helpers/api/pm/projects_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::Pm::ProjectsHelper. For example:
+#
+# describe Api::Pm::ProjectsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::Pm::ProjectsHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/api/v1/pm_issues_helper_spec.rb b/spec/helpers/api/v1/pm_issues_helper_spec.rb
new file mode 100644
index 000000000..4b67710fb
--- /dev/null
+++ b/spec/helpers/api/v1/pm_issues_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::V1::PmIssuesHelper. For example:
+#
+# describe Api::V1::PmIssuesHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::V1::PmIssuesHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/api/v1/sonarqubes_helper_spec.rb b/spec/helpers/api/v1/sonarqubes_helper_spec.rb
new file mode 100644
index 000000000..9021add1e
--- /dev/null
+++ b/spec/helpers/api/v1/sonarqubes_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::V1::SonarqubesHelper. For example:
+#
+# describe Api::V1::SonarqubesHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::V1::SonarqubesHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/pm/journals_helper_spec.rb b/spec/helpers/pm/journals_helper_spec.rb
new file mode 100644
index 000000000..8dd212826
--- /dev/null
+++ b/spec/helpers/pm/journals_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Pm::JournalsHelper. For example:
+#
+# describe Pm::JournalsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Pm::JournalsHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/daily_platform_statistic_spec.rb b/spec/models/daily_platform_statistic_spec.rb
new file mode 100644
index 000000000..0f6d5fcdd
--- /dev/null
+++ b/spec/models/daily_platform_statistic_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe DailyPlatformStatistic, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/gitlink_competition_apply_spec.rb b/spec/models/gitlink_competition_apply_spec.rb
new file mode 100644
index 000000000..8b05ff4ad
--- /dev/null
+++ b/spec/models/gitlink_competition_apply_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe GitlinkCompetitionApply, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/pm_link_spec.rb b/spec/models/pm_link_spec.rb
new file mode 100644
index 000000000..d911d5b7e
--- /dev/null
+++ b/spec/models/pm_link_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe PmLink, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end