diff --git a/.gitignore b/.gitignore
index fbed31a8e..2eac1d277 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,4 +84,5 @@ redis_data/
Dockerfile
dump.rdb
.tags*
-ceshi_user.xlsx
\ No newline at end of file
+ceshi_user.xlsx
+public/trace_task_results
\ No newline at end of file
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index a837e952f..b80282798 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -1,6 +1,25 @@
class AccountsController < ApplicationController
+ before_action :require_login, only: [:login_check, :simple_update]
include ApplicationHelper
+ #skip_before_action :check_account, :only => [:logout]
+
+ def simple_update
+ simple_update_params.merge!(username: params[:username]&.gsub(/\s+/, ""))
+ simple_update_params.merge!(email: params[:email]&.gsub(/\s+/, ""))
+ simple_update_params.merge!(platform: (params[:platform] || 'forge')&.gsub(/\s+/, ""))
+ Register::RemoteForm.new(simple_update_params).validate!
+
+ ActiveRecord::Base.transaction do
+ result = auto_update(current_user, simple_update_params)
+ if result[:message].blank?
+ render_ok
+ else
+ render_error(result[:message])
+ end
+ end
+ end
+
def index
render json: session
end
@@ -315,6 +334,11 @@ class AccountsController < ApplicationController
Register::CheckColumnsForm.new(check_params).validate!
render_ok
end
+
+ def login_check
+ Register::LoginCheckColumnsForm.new(check_params.merge(user: current_user)).validate!
+ render_ok
+ end
private
@@ -383,4 +407,7 @@ class AccountsController < ApplicationController
params.permit(:username, :email, :password, :platform)
end
+ def simple_update_params
+ params.permit(:username, :email, :password, :platform)
+ end
end
diff --git a/app/controllers/admins/import_users_controller.rb b/app/controllers/admins/import_users_controller.rb
index 5d8a60ce6..4575b08d5 100644
--- a/app/controllers/admins/import_users_controller.rb
+++ b/app/controllers/admins/import_users_controller.rb
@@ -2,7 +2,7 @@ class Admins::ImportUsersController < Admins::BaseController
def create
return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile)
- result = Admins::ImportUserService.call(params[:file].to_io)
+ result = Admins::ImportUserFromExcelService.call(params[:file].to_io)
render_ok(result)
rescue Admins::ImportUserService::Error => ex
render_error(ex)
diff --git a/app/controllers/admins/message_templates_controller.rb b/app/controllers/admins/message_templates_controller.rb
index 23c94e784..af77858e1 100644
--- a/app/controllers/admins/message_templates_controller.rb
+++ b/app/controllers/admins/message_templates_controller.rb
@@ -2,8 +2,24 @@ class Admins::MessageTemplatesController < Admins::BaseController
before_action :get_template, only: [:edit, :update, :destroy]
def index
- message_templates = MessageTemplate.group(:type).count.keys
- @message_templates = kaminari_array_paginate(message_templates)
+ message_templates = MessageTemplate.ransack(sys_notice_or_email_or_email_title_cont: params[:search]).result
+ @message_templates = kaminari_paginate(message_templates)
+ end
+
+ def new
+ @message_template = MessageTemplate::CustomTip.new
+ end
+
+ def create
+ @message_template = MessageTemplate::CustomTip.new(ignore_params)
+
+ if @message_template.save!
+ redirect_to admins_message_templates_path
+ flash[:success] = "创建消息模板成功"
+ else
+ render :new
+ flash[:danger] = "创建消息模板失败"
+ end
end
def edit
diff --git a/app/controllers/ci/cloud_accounts_controller.rb b/app/controllers/ci/cloud_accounts_controller.rb
index 7d829dbd7..d22b2ba41 100644
--- a/app/controllers/ci/cloud_accounts_controller.rb
+++ b/app/controllers/ci/cloud_accounts_controller.rb
@@ -14,12 +14,12 @@ class Ci::CloudAccountsController < Ci::BaseController
def create
flag, msg = check_bind_cloud_account!
- return render_error(msg) if flag === true
+ return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do
@cloud_account = bind_account!
if @cloud_account.blank?
- render_error('激活失败, 请检查你的云服务器信息是否正确.')
+ tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback
else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
@@ -27,17 +27,17 @@ class Ci::CloudAccountsController < Ci::BaseController
end
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def activate
- return render_error('请先认证') unless current_user.ci_certification?
+ return tip_exception('请先认证') unless current_user.ci_certification?
begin
@cloud_account = Ci::CloudAccount.find params[:id]
ActiveRecord::Base.transaction do
if @repo
- return render_error('该项目已经激活') if @repo.repo_active?
+ return tip_exception('该项目已经激活') if @repo.repo_active?
@repo.activate!(@project)
else
@repo = Ci::Repo.auto_create!(@ci_user, @project)
@@ -50,7 +50,7 @@ class Ci::CloudAccountsController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
end
@@ -59,39 +59,39 @@ class Ci::CloudAccountsController < Ci::BaseController
def bind
flag, msg = check_bind_cloud_account!
- return render_error(msg) if flag === true
+ return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do
@cloud_account = bind_account!
if @cloud_account.blank?
- render_error('激活失败, 请检查你的云服务器信息是否正确.')
+ tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback
else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
end
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def trustie_bind
account = params[:account].to_s
- return render_error("account不能为空.") if account.blank?
+ return tip_exception("account不能为空.") if account.blank?
flag, msg = check_trustie_bind_cloud_account!
- return render_error(msg) if flag === true
+ return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do
@cloud_account = trustie_bind_account!
if @cloud_account.blank?
- render_error('激活失败, 请检查你的云服务器信息是否正确.')
+ tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback
else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
end
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def unbind
@@ -107,18 +107,18 @@ class Ci::CloudAccountsController < Ci::BaseController
render_ok
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def oauth_grant
password = params[:password].to_s
- return render_error('你输入的密码不正确.') unless current_user.check_password?(password)
+ return tip_exception('你输入的密码不正确.') unless current_user.check_password?(password)
oauth = current_user.oauths.last
- return render_error("服务器出小差了.") if oauth.blank?
+ return tip_exception("服务器出小差了.") if oauth.blank?
result = gitea_oauth_grant!(password, oauth)
- return render_error('授权失败.') unless result === true
+ return tip_exception('授权失败.') unless result === true
current_user.set_drone_step!(User::DEVOPS_CERTIFICATION)
end
diff --git a/app/controllers/ci/pipelines_controller.rb b/app/controllers/ci/pipelines_controller.rb
index ea8b25f77..d6eee1387 100644
--- a/app/controllers/ci/pipelines_controller.rb
+++ b/app/controllers/ci/pipelines_controller.rb
@@ -30,7 +30,7 @@ class Ci::PipelinesController < Ci::BaseController
ActiveRecord::Base.transaction do
size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size
if size > 0
- render_error("#{params[:branch]}分支已经存在流水线!")
+ tip_exception("#{params[:branch]}分支已经存在流水线!")
return
end
pipeline = Ci::Pipeline.new(pipeline_name: params[:pipeline_name], file_name: params[:file_name],owner: params[:owner],
@@ -53,7 +53,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok({id: pipeline.id})
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
# 在代码库创建文件
@@ -81,6 +81,7 @@ class Ci::PipelinesController < Ci::BaseController
repo_branch: pipeline.branch,
repo_config: pipeline.file_name
}
+ Rails.logger.info("########create_params===#{create_params.to_json}")
repo = Ci::Repo.create_repo(create_params)
repo
end
@@ -118,7 +119,7 @@ class Ci::PipelinesController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def destroy
@@ -132,7 +133,7 @@ class Ci::PipelinesController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def content
@@ -182,7 +183,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def update_stage
@@ -192,7 +193,7 @@ class Ci::PipelinesController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def delete_stage
@@ -205,7 +206,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def update_stage_index(pipeline_id, show_index, diff)
@@ -229,7 +230,7 @@ class Ci::PipelinesController < Ci::BaseController
unless steps.empty?
steps.each do |step|
unless step[:template_id]
- render_error('请选择模板!')
+ tip_exception('请选择模板!')
return
end
if !step[:id]
@@ -246,7 +247,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def create_stage_step
@@ -262,7 +263,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def update_stage_step
@@ -279,7 +280,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok
end
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def delete_stage_step
@@ -289,6 +290,6 @@ class Ci::PipelinesController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 69644e7a5..53f11fb23 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -30,19 +30,19 @@ class Ci::ProjectsController < Ci::BaseController
@file = interactor.result
render_result(1, "更新成功")
else
- render_error(interactor.error)
+ tip_exception(interactor.error)
end
end
def activate
- return render_error('你还未认证') unless current_user.ci_certification?
+ return tip_exception('你还未认证') unless current_user.ci_certification?
begin
ActiveRecord::Base.transaction do
if @repo
- return render_error('该项目已经激活') if @repo.repo_active?
+ @repo.destroy! if @repo&.repo_user_id == 0
+ return tip_exception('该项目已经激活') if @repo.repo_active?
@repo.activate!(@project)
- return render_ok
else
@repo = Ci::Repo.auto_create!(@ci_user, @project)
@ci_user.update_column(:user_syncing, false)
@@ -55,12 +55,12 @@ class Ci::ProjectsController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
end
def deactivate
- return render_error('该项目已经取消激活') if !@repo.repo_active?
+ return tip_exception('该项目已经取消激活') if !@repo.repo_active?
@project.update_column(:open_devops, false)
@repo.deactivate_repos!
diff --git a/app/controllers/ci/secrets_controller.rb b/app/controllers/ci/secrets_controller.rb
index 324f0bb53..3057824e2 100644
--- a/app/controllers/ci/secrets_controller.rb
+++ b/app/controllers/ci/secrets_controller.rb
@@ -20,14 +20,14 @@ class Ci::SecretsController < Ci::BaseController
if result["id"]
render_ok
else
- render_error(result["message"])
+ tip_exception(result["message"])
end
else
result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret
if result["id"]
render_ok
else
- render_error(result["message"])
+ tip_exception(result["message"])
end
end
end
@@ -39,7 +39,7 @@ class Ci::SecretsController < Ci::BaseController
Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], {name: name}).delete_secret
render_ok
else
- render_error("参数名不能为空")
+ tip_exception("参数名不能为空")
end
rescue Exception => ex
render_ok
diff --git a/app/controllers/ci/templates_controller.rb b/app/controllers/ci/templates_controller.rb
index cb49590dc..22c209523 100644
--- a/app/controllers/ci/templates_controller.rb
+++ b/app/controllers/ci/templates_controller.rb
@@ -50,7 +50,7 @@ class Ci::TemplatesController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def update
@@ -63,7 +63,7 @@ class Ci::TemplatesController < Ci::BaseController
)
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
def destroy
@@ -73,7 +73,7 @@ class Ci::TemplatesController < Ci::BaseController
end
render_ok
rescue Exception => ex
- render_error(ex.message)
+ tip_exception(ex.message)
end
#======流水线模板查询=====#
diff --git a/app/controllers/concerns/ci/cloud_account_manageable.rb b/app/controllers/concerns/ci/cloud_account_manageable.rb
index 9f0998edc..f2157657e 100644
--- a/app/controllers/concerns/ci/cloud_account_manageable.rb
+++ b/app/controllers/concerns/ci/cloud_account_manageable.rb
@@ -160,9 +160,9 @@ module Ci::CloudAccountManageable
state = SecureRandom.hex(8)
# redirect_uri eg:
# https://localhost:3000/login/oauth/authorize?client_id=94976481-ad0e-4ed4-9247-7eef106007a2&redirect_uri=http%3A%2F%2F121.69.81.11%3A80%2Flogin&response_type=code&state=9cab990b9cfb1805
- redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login")
- clientId = client_id(oauth)
- grant_url = "#{Gitea.gitea_config[:domain]}/login/oauth/authorize?client_id=#{clientId}&redirect_uri=#{redirect_uri}&response_type=code&state=#{state}"
+ # redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login")
+ # clientId = client_id(oauth)
+ grant_url = "#{@cloud_account.drone_url}/login"
logger.info "[gitea] grant_url: #{grant_url}"
conn = Faraday.new(url: grant_url) do |req|
@@ -179,7 +179,7 @@ module Ci::CloudAccountManageable
def drone_oauth_user!(url, state)
logger.info "[drone] drone_oauth_user url: #{url}"
- conn = Faraday.new(url: "#{Gitea.gitea_config[:domain]}#{url}") do |req|
+ conn = Faraday.new(url: url) do |req|
req.request :url_encoded
req.adapter Faraday.default_adapter
req.headers["cookie"] = "_session_=#{SecureRandom.hex(28)}; _oauth_state_=#{state}"
@@ -188,8 +188,8 @@ module Ci::CloudAccountManageable
response = conn.get
logger.info "[drone] response headers: #{response.headers}"
- true
- # response.headers['location'].include?('error') ? false : true
+ # true
+ response.headers['location'].include?('error') ? false : true
end
private
diff --git a/app/controllers/concerns/register_helper.rb b/app/controllers/concerns/register_helper.rb
index 9d2f420a0..2e910d8c4 100644
--- a/app/controllers/concerns/register_helper.rb
+++ b/app/controllers/concerns/register_helper.rb
@@ -58,4 +58,32 @@ module RegisterHelper
end
end
+ def auto_update(user, params={})
+ return if params.blank?
+ result = {message: nil, user: nil}
+ before_login = user.login
+ user.login = params[:username]
+ user.password = params[:password]
+ user.mail = params[:email]
+
+ if user.save!
+ sync_params = {
+ password: params[:password].to_s,
+ email: params[:email],
+ login_name: params[:username],
+ new_name: params[:username],
+ source_id: 0
+ }
+
+ interactor = Gitea::User::UpdateInteractor.call(before_login, sync_params)
+ if interactor.success?
+ result[:user] = user
+ else
+ result[:message] = '用户同步Gitea失败!'
+ end
+ else
+ result[:message] = user.errors.full_messages.join(",")
+ return
+ end
+ end
end
diff --git a/app/controllers/concerns/render_helper.rb b/app/controllers/concerns/render_helper.rb
index b54ac90ce..851a3ccf8 100644
--- a/app/controllers/concerns/render_helper.rb
+++ b/app/controllers/concerns/render_helper.rb
@@ -4,7 +4,7 @@ module RenderHelper
end
def render_error(message = '')
- render json: { status: -1, message: message }
+ render json: { status: status, message: message }
end
def render_not_acceptable(message = '请求已拒绝')
diff --git a/app/controllers/notices_controller.rb b/app/controllers/notices_controller.rb
index bf7eb21b1..7b67412e7 100644
--- a/app/controllers/notices_controller.rb
+++ b/app/controllers/notices_controller.rb
@@ -1,7 +1,7 @@
class NoticesController < ApplicationController
def create
- tip_exception("参数有误") if params["source"].blank?
+ return tip_exception("参数有误") if params["source"].blank?
user_id = params[:user_id]
if params["source"] == "CompetitionBegin"
@@ -13,9 +13,21 @@ class NoticesController < ApplicationController
elsif params["source"] == "CompetitionReview"
competition_id = params[:competition_id]
SendTemplateMessageJob.perform_later('CompetitionReview', user_id, competition_id)
+ elsif params["source"] == "CustomTip"
+ users_id = params[:users_id]
+ props = params[:props].to_unsafe_hash
+ return tip_exception("参数有误") unless props.is_a?(Hash) && users_id.is_a?(Array)
+ template_id = params[:template_id]
+ SendTemplateMessageJob.perform_later('CustomTip', users_id, template_id, props)
else
tip_exception("#{params["source"]}未配置")
end
render_ok
end
+
+
+ private
+ def params_props
+ params.require(:notice).permit(:props)
+ end
end
diff --git a/app/controllers/owners_controller.rb b/app/controllers/owners_controller.rb
index bc4be96c8..28a5210d5 100644
--- a/app/controllers/owners_controller.rb
+++ b/app/controllers/owners_controller.rb
@@ -12,7 +12,7 @@ class OwnersController < ApplicationController
def show
@owner = Owner.find_by(login: params[:id]) || Owner.find_by(id: params[:id])
- return render_ok(type: 'User') unless @owner.present?
+ return render_not_found unless @owner.present?
# 组织
if @owner.is_a?(Organization)
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 8bc2fb476..68c4e59fb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -22,6 +22,7 @@ class ProjectsController < ApplicationController
menu.append(menu_hash_by_name("devops")) if @project.has_menu_permission("devops") && @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))
menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") && @project.forge?
menu.append(menu_hash_by_name("activity"))
menu.append(menu_hash_by_name("settings")) if user_is_admin && @project.forge?
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 9fef22fc5..86cef7f32 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -225,7 +225,8 @@ class RepositoriesController < ApplicationController
@path = Gitea.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/"
@readme = result[:status] === :success ? result[:body] : nil
@readme['content'] = decode64_content(@readme, @owner, @repository, params[:ref], @path)
- render json: @readme.slice("type", "encoding", "size", "name", "path", "content", "sha")
+ @readme['replace_content'] = readme_decode64_content(@readme, @owner, @repository, params[:ref], @path)
+ render json: @readme.slice("type", "encoding", "size", "name", "path", "content", "sha", "replace_content")
rescue
render json: nil
end
@@ -387,4 +388,4 @@ class RepositoriesController < ApplicationController
end
end
-end
+end
\ No newline at end of file
diff --git a/app/controllers/traces/base_controller.rb b/app/controllers/traces/base_controller.rb
new file mode 100644
index 000000000..2b857d232
--- /dev/null
+++ b/app/controllers/traces/base_controller.rb
@@ -0,0 +1,18 @@
+class Traces::BaseController < ApplicationController
+
+ helper_method :observed_logged_user?, :observed_user
+
+
+ def observed_user
+ @_observed_user ||= (User.find_by_login(params[:user_id]) || User.find_by_id(params[:user_id]))
+ end
+
+ def observed_logged_user?
+ observed_user.id == User.current&.id
+ end
+
+ protected
+ def check_auth
+ return render_forbidden unless current_user.admin? || observed_logged_user?
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/traces/projects_controller.rb b/app/controllers/traces/projects_controller.rb
new file mode 100644
index 000000000..62573a1d8
--- /dev/null
+++ b/app/controllers/traces/projects_controller.rb
@@ -0,0 +1,83 @@
+class Traces::ProjectsController < Traces::BaseController
+ include OperateProjectAbilityAble
+
+ before_action :require_login
+ before_action :load_project
+ before_action :authorizate_user_can_edit_project!, except: [:task_results]
+
+ def tasks
+ branch_name = params[:branch_name]
+ return render_error("无可用检测次数") if @project.user_trace_tasks.size >= 5
+ return render_error("分支名不能为空!") if branch_name.blank?
+ @all_branches = Gitea::Repository::Branches::ListNameService.call(@project&.owner, @project.identifier)
+ return render_error("请输入正确的分支名!") unless @all_branches["branch_name"].include?(branch_name)
+ code, data, error = Trace::CheckService.call(current_user.trace_token, @project, "1", branch_name)
+ if code == 200
+ UserTraceTask.create!(
+ user_id: current_user.id,
+ project_id: @project.id,
+ branch_tag: branch_name,
+ task_id: data["task_id"]
+ )
+ render_ok
+ else
+ render_error("检测失败 Error:#{error}")
+ end
+ rescue Exception => exception
+ puts exception.message
+ normal_status(-1, exception.message)
+ end
+
+ def task_results
+ limit = params[:limit] || params[:per_page]
+ limit = (limit.to_i.zero? || limit.to_i > 15) ? 15 : limit.to_i
+ page = params[:page].to_i.zero? ? 1 : params[:page].to_i
+ return render :json => {left_tasks_count: 5, data: []} if current_user.trace_user.nil?
+ code, data, error = Trace::CheckResultService.call(current_user.trace_token, @project, nil, page, limit)
+ if code == 200
+ render :json => {left_tasks_count: 5 - @project.user_trace_tasks.size, data: data}
+ else
+ render_error("获取检测记录失败 Error:#{error}")
+ end
+ rescue Exception => exception
+ puts exception.message
+ normal_status(-1, exception.message)
+ end
+
+ def reload_task
+ return render_error("project_id错误") if params[:project_id].blank?
+ branch_name = params[:branch_name]
+ return render_error("分支名不能为空!") if branch_name.blank?
+ @all_branches = Gitea::Repository::Branches::ListNameService.call(@project&.owner, @project.identifier)
+ return render_error("请输入正确的分支名!") unless @all_branches["branch_name"].include?(branch_name)
+ code, data, error = Trace::ReloadCheckService.call(current_user.trace_token, params[:project_id])
+ if code == 200
+ UserTraceTask.create!(
+ user_id: current_user.id,
+ project_id: @project.id,
+ branch_tag: branch_name,
+ task_id: data["task_id"]
+ )
+ render_ok
+ else
+ render_error("重新检测失败 Error:#{error}")
+ end
+ rescue Exception => exception
+ puts exception.message
+ normal_status(-1, exception.message)
+ end
+
+
+ def task_pdf
+ return render_error("task_id错误") if params[:task_id].blank?
+ result = Trace::PdfReportService.call(current_user.trace_token, params[:task_id])
+ if result.is_a?(Hash) && result[:code] == 200
+ redirect_to result[:download_url]
+ else
+ render_error("下载报告失败!")
+ end
+ rescue Exception => exception
+ puts exception.message
+ normal_status(-1, exception.message)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/traces/trace_users_controller.rb b/app/controllers/traces/trace_users_controller.rb
new file mode 100644
index 000000000..0b28e905a
--- /dev/null
+++ b/app/controllers/traces/trace_users_controller.rb
@@ -0,0 +1,14 @@
+class Traces::TraceUsersController < Traces::BaseController
+ before_action :require_login
+
+ def create
+ if current_user.trace_token.present?
+ render_ok
+ else
+ render_error(-1, "代码溯源用户初始化失败")
+ end
+ rescue Exception => exception
+ puts exception.message
+ normal_status(-1, exception.message)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index f61ba2478..056909f64 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,332 +1,399 @@
-class UsersController < ApplicationController
- include ApplicationHelper
- include Ci::DbConnectable
-
- before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard]
- before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard]
- before_action :require_login, only: %i[me sync_user_info]
- before_action :connect_to_ci_db, only: [:get_user_info]
- before_action :convert_image!, only: [:update, :update_image]
- skip_before_action :check_sign, only: [:attachment_show]
-
- def connect_to_ci_db(options={})
- if !(current_user && !current_user.is_a?(AnonymousUser) && current_user.devops_certification?)
- return
- end
- if current_user.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE
- connect_to_trustie_ci_database(options)
- else
- connect_to_ci_database(options)
- end
- end
-
- def list
- scope = User.active.recent.like(params[:search]).includes(:user_extension)
- @total_count = scope.size
- @users = paginate(scope)
- end
-
- def show
- #待办事项,现在未做
- if User.current.admin? || User.current.login == @user.login
- @waiting_applied_messages = @user.applied_messages.waiting
- @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common
- @common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common
- #@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size
- @undo_events = @common_applied_transfer_projects.size + @common_applied_projects.size
- else
- @waiting_applied_messages = AppliedMessage.none
- @common_applied_transfer_projects = AppliedTransferProject.none
- @common_applied_projects = AppliedProject.none
- @undo_events = 0
- end
- #用户的组织数量
- # @user_composes_count = @user.composes.size
- @user_composes_count = 0
- user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common")
- @user_org_count = user_organizations.size
- normal_projects = Project.members_projects(@user.id).to_sql
- org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql
- projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
- user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible
- @projects_common_count = user_projects.common.size
- @projects_mirrior_count = user_projects.mirror.size
- @projects_sync_mirrior_count = user_projects.sync_mirror.size
- # 为了缓存活跃用户的基本信息,后续删除
- Cache::V2::OwnerCommonService.new(@user.id).read
- end
-
- def watch_users
- watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc")
- if params[:search].present?
- search_user_ids = User.where(id: watchers.pluck(:watchable_id)).like(params[:search]).pluck(:id)
- watchers = watchers.where(watchable_id: search_user_ids)
- end
- @watchers_count = watchers.size
- @watchers = paginate(watchers)
- end
-
- def fan_users
- watchers = @user.watchers.includes(:user).order("watchers.created_at desc")
- watchers = watchers.joins(:user).merge(User.like(params[:search]))
- @watchers_count = watchers.size
- @watchers = paginate(watchers)
- end
-
- def hovercard
- end
-
- def update
- return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
- return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
- Util.write_file(@image, avatar_path(@user)) if user_params[:image].present?
- @user.attributes = user_params.except(:image)
- unless @user.save
- render_error(-1, @user.errors.full_messages.join(", "))
- end
- end
-
- def update_image
- return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
- return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
-
- Util.write_file(@image, avatar_path(@user))
- return render_ok({message: '头像修改成功'})
- rescue Exception => e
- uid_logger_error(e.message)
- render_error(-1, '头像修改失败!')
- end
-
- def get_image
- return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
- return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
-
- redirect_to Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(@user).to_s
- end
-
- def me
- @user = current_user
- end
-
- # 贴吧获取用户信接口
- def get_user_info
- begin
- @user = current_user
- begin
- result = Notice::Read::CountService.call(current_user.id)
- @message_unread_total = result.nil? ? 0 : result[2]["unread_notification"]
- rescue
- @message_unread_total = 0
- end
- # TODO 等消息上线再打开注释
- #@tidding_count = unviewed_tiddings(current_user) if current_user.present?
- rescue Exception => e
- uid_logger_error(e.message)
- missing_template
- end
-
- end
-
- def attachment_show
- file_name = params[:file_name]
- path = params[:path] || file_storage_directory
- send_file "#{path}/#{file_name}", :filename => "#{file_name}",
- :type => 'game',
- :disposition => 'attachment' #inline can open in browser
- end
-
- def html_show
- @contents = File.read("#{params[:path]}")
- respond_to do |format|
- format.html {render :layout => false}
- end
- end
-
- # Redo: 消息总数缓存
- def get_navigation_info
- # @old_domain = edu_setting('old_edu_host')
- # @user = current_user
- # # 新消息数
- # @new_message = @user.tidings.where("created_at > '#{@user.click_time}'").count > 0 || @user.private_messages.where("created_at > '#{@user.click_time}'").count > 0
- #
- # @user_url = "/users/#{@user.login}"
- # @career = Career.where(status: true).order("created_at asc").pluck(:id, :name)
- # @auth = User.current.ec_school.present? ? "#{@old_domain}/ecs/department?school_id=#{User.current.ec_school}" : nil
- end
-
- # 用户回复功能
- def reply_message
- message = JournalsForMessage.new(reply_message_params)
- message.user_id = current_user.id
- message.save!
-
- render_ok(id: message.id)
- end
-
- # 搜索用户具有管理员角色的项目
- def search_user_projects
- projects = Project.where.not(status: 9)
-
- projects = projects.joins(members: :member_roles).where(member_roles: { role_id: 3 })
- projects = projects.where(members: { user_id: current_user.id })
-
- search = params[:search].to_s.strip
- projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present?
-
- @projects = projects.select(:id, :name)
- end
-
- #TODO 个人主页信息,forge上弃用-hs, 0602
- def homepage_info
- #待办事项,现在未做
- @undo_events = 10
- #用户的组织数量
- # @user_composes_count = @user.composes.size
- @user_composes_count = 10
- end
-
- def brief_introduction
- content = params[:content].to_s.strip
-
- current_user.user_extension.update!(brief_introduction: content)
-
- render_ok
- end
-
- def attendance
- attendance = Users::AttendanceService.call(current_user)
- render_ok(grade: current_user.grade, next_gold: attendance.next_gold)
- rescue Users::AttendanceService::Error => ex
- render_error(ex.message)
- end
-
- # 其他平台登录后,必须将token同步到forge平台,实现sso登录功能
- def sync_token
- return render_error('未找相关用户!') unless @user
-
- token = Token.get_or_create_permanent_login_token(@user, 'autologin')
- token.update_column(:value, params[:token])
- render_ok
- end
-
- def trustie_related_projects
- projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc")
- projects_json = []
- domain_url = EduSetting.get('host_name')
- if projects.present?
- projects.each do |p|
- project_url = "/#{p.owner.login}/#{p.identifier}"
- pj = {
- id: p.id,
- name: p.name,
- is_public: p.is_public,
- updated_on: p.updated_on.strftime("%Y-%m-%d"),
- status: p.status,
- is_member: p.member?(current_user.try(:id)),
- owner: {
- name: p.owner.try(:show_real_name),
- login: p.owner.login
- },
- members_count: p&.members.size,
- issues_count: p.issues_count - p.pull_requests_count,
- commits_count: p&.project_score&.changeset_num.to_i,
- http_url: domain_url + project_url,
- http_collaborator_url: domain_url + project_url + "/setting/collaborator",
- http_issues_url: domain_url + project_url + "/issues",
- http_commits_url: domain_url + project_url + "/commits",
- project_score: p&.project_score.present? ? p&.project_score&.as_json(:except=>[:created_at, :updated_at]).merge!(commit_time: format_time(p&.project_score&.commit_time)) : {}
- }
- projects_json.push(pj)
- end
- end
- Rails.logger.info("==========projects_json========+########{projects_json}")
- render json: { projects: projects_json.present? ? projects_json : {} }
- end
-
- def trustie_projects
- user_id = User.select(:id, :login).where(login: params[:login])&.first&.id
- projects = Project.visible
-
- projects = projects.joins(:members).where(members: { user_id: user_id })
-
- search = params[:search].to_s.strip
- projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present?
-
- projects = projects.select(:id, :name).limit(10).as_json
- render json: { projects: projects }
- end
-
- def projects
- is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
- scope = Projects::ListMyQuery.call(params, @user,is_current_admin_user)
- @total_count = scope.size
- @projects = paginate(scope)
- end
-
- # TODO 其他平台登录时同步修改gitea平台对应用户的密码
- # 该方法主要用于:别的平台初次部署对接forge平台,同步用户后,gitea平台对应的用户密码与forge平台用户密码不一致是问题
- def sync_gitea_pwd
- return render_error("未找到相关的用户") if @user.blank?
-
- flag = sync_pwd_to_gitea!(@user, {password: params[:password].to_s})
- flag ? render_ok : render_error('同步失败!')
- end
-
- # TODO
- # 同步trusite平台用户的salt信息,只需同步一次,同步完成后,该方法可以删除
- def sync_salt
- user = User.find_by_login params[:login]
- return if user.blank?
- user.update_column(:salt, params[:salt])
- render_ok
- end
-
- def sync_user_info
- user = User.find_by_login params[:login]
- return render_forbidden unless user === current_user
-
- sync_params = {
- email: params[:email],
- password: params[:password]
- }
-
- Users::UpdateInfoForm.new(sync_params.merge(login: params[:login])).validate!
-
- interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params)
- if interactor.success?
- user.update!(password: params[:password], mail: params[:email], status: User::STATUS_ACTIVE)
- render_ok
- else
- render_error(interactor.error)
- end
- end
-
- private
- def load_user
- @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id])
- end
-
- def user_params
- params.require(:user).permit(:nickname, :image,
- user_extension_attributes: [
- :gender, :location, :location_city,
- :occupation, :technical_title,
- :school_id, :department_id, :province, :city,
- :custom_department, :identity, :student_id, :description,
- :show_super_description, :super_description,
- :show_email, :show_location, :show_department]
- )
- end
-
- def reply_message_params
- normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? ||
- params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil?
- params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id)
- end
-
- def check_user_exist
- return if @user.present?
- render_not_found
- end
-
-end
\ No newline at end of file
+class UsersController < ApplicationController
+ include ApplicationHelper
+ include Ci::DbConnectable
+
+ before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard]
+ before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard]
+ before_action :require_login, only: %i[me sync_user_info]
+ before_action :connect_to_ci_db, only: [:get_user_info]
+ before_action :convert_image!, only: [:update, :update_image]
+ skip_before_action :check_sign, only: [:attachment_show]
+ before_action :sso_login, only: [:get_user_info]
+
+ def connect_to_ci_db(options={})
+ if !(current_user && !current_user.is_a?(AnonymousUser) && current_user.devops_certification?)
+ return
+ end
+ if current_user.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE
+ connect_to_trustie_ci_database(options)
+ else
+ connect_to_ci_database(options)
+ end
+ end
+
+ def list
+ scope = User.active.recent.like(params[:search]).includes(:user_extension)
+ @total_count = scope.size
+ @users = paginate(scope)
+ end
+
+ def show
+ #待办事项,现在未做
+ if User.current.admin? || User.current.login == @user.login
+ @waiting_applied_messages = @user.applied_messages.waiting
+ @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common
+ @common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common
+ #@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size
+ @undo_events = @common_applied_transfer_projects.size + @common_applied_projects.size
+ else
+ @waiting_applied_messages = AppliedMessage.none
+ @common_applied_transfer_projects = AppliedTransferProject.none
+ @common_applied_projects = AppliedProject.none
+ @undo_events = 0
+ end
+ #用户的组织数量
+ # @user_composes_count = @user.composes.size
+ @user_composes_count = 0
+ user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common")
+ @user_org_count = user_organizations.size
+ normal_projects = Project.members_projects(@user.id).to_sql
+ org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql
+ projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
+ user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible
+ @projects_common_count = user_projects.common.size
+ @projects_mirrior_count = user_projects.mirror.size
+ @projects_sync_mirrior_count = user_projects.sync_mirror.size
+ # 为了缓存活跃用户的基本信息,后续删除
+ Cache::V2::OwnerCommonService.new(@user.id).read
+ end
+
+ def watch_users
+ watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc")
+ if params[:search].present?
+ search_user_ids = User.where(id: watchers.pluck(:watchable_id)).like(params[:search]).pluck(:id)
+ watchers = watchers.where(watchable_id: search_user_ids)
+ end
+ @watchers_count = watchers.size
+ @watchers = paginate(watchers)
+ end
+
+ def fan_users
+ watchers = @user.watchers.includes(:user).order("watchers.created_at desc")
+ watchers = watchers.joins(:user).merge(User.like(params[:search]))
+ @watchers_count = watchers.size
+ @watchers = paginate(watchers)
+ end
+
+ def hovercard
+ end
+
+ def update
+ return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
+ return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
+ Util.write_file(@image, avatar_path(@user)) if user_params[:image].present?
+ @user.attributes = user_params.except(:image)
+ unless @user.save
+ render_error(-1, @user.errors.full_messages.join(", "))
+ end
+ end
+
+ def update_image
+ return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
+ return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
+
+ Util.write_file(@image, avatar_path(@user))
+ return render_ok({message: '头像修改成功'})
+ rescue Exception => e
+ uid_logger_error(e.message)
+ render_error(-1, '头像修改失败!')
+ end
+
+ def get_image
+ return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
+ return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
+
+ redirect_to Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(@user).to_s
+ end
+
+ def me
+ @user = current_user
+ end
+
+ # 贴吧获取用户信接口
+ def get_user_info
+ begin
+ @user = current_user
+ begin
+ result = Notice::Read::CountService.call(current_user.id)
+ @message_unread_total = result.nil? ? 0 : result[2]["unread_notification"]
+ rescue
+ @message_unread_total = 0
+ end
+ # TODO 等消息上线再打开注释
+ #@tidding_count = unviewed_tiddings(current_user) if current_user.present?
+ rescue Exception => e
+ uid_logger_error(e.message)
+ missing_template
+ end
+
+ end
+
+ def attachment_show
+ file_name = params[:file_name]
+ path = params[:path] || file_storage_directory
+ send_file "#{path}/#{file_name}", :filename => "#{file_name}",
+ :type => 'game',
+ :disposition => 'attachment' #inline can open in browser
+ end
+
+ def html_show
+ @contents = File.read("#{params[:path]}")
+ respond_to do |format|
+ format.html {render :layout => false}
+ end
+ end
+
+ # Redo: 消息总数缓存
+ def get_navigation_info
+ # @old_domain = edu_setting('old_edu_host')
+ # @user = current_user
+ # # 新消息数
+ # @new_message = @user.tidings.where("created_at > '#{@user.click_time}'").count > 0 || @user.private_messages.where("created_at > '#{@user.click_time}'").count > 0
+ #
+ # @user_url = "/users/#{@user.login}"
+ # @career = Career.where(status: true).order("created_at asc").pluck(:id, :name)
+ # @auth = User.current.ec_school.present? ? "#{@old_domain}/ecs/department?school_id=#{User.current.ec_school}" : nil
+ end
+
+ # 用户回复功能
+ def reply_message
+ message = JournalsForMessage.new(reply_message_params)
+ message.user_id = current_user.id
+ message.save!
+
+ render_ok(id: message.id)
+ end
+
+ # 搜索用户具有管理员角色的项目
+ def search_user_projects
+ projects = Project.where.not(status: 9)
+
+ projects = projects.joins(members: :member_roles).where(member_roles: { role_id: 3 })
+ projects = projects.where(members: { user_id: current_user.id })
+
+ search = params[:search].to_s.strip
+ projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present?
+
+ @projects = projects.select(:id, :name)
+ end
+
+ #TODO 个人主页信息,forge上弃用-hs, 0602
+ def homepage_info
+ #待办事项,现在未做
+ @undo_events = 10
+ #用户的组织数量
+ # @user_composes_count = @user.composes.size
+ @user_composes_count = 10
+ end
+
+ def brief_introduction
+ content = params[:content].to_s.strip
+
+ current_user.user_extension.update!(brief_introduction: content)
+
+ render_ok
+ end
+
+ def attendance
+ attendance = Users::AttendanceService.call(current_user)
+ render_ok(grade: current_user.grade, next_gold: attendance.next_gold)
+ rescue Users::AttendanceService::Error => ex
+ render_error(ex.message)
+ end
+
+ # 其他平台登录后,必须将token同步到forge平台,实现sso登录功能
+ def sync_token
+ return render_error('未找相关用户!') unless @user
+
+ token = Token.get_or_create_permanent_login_token(@user, 'autologin')
+ token.update_column(:value, params[:token])
+ render_ok
+ end
+
+ def trustie_related_projects
+ projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc")
+ projects_json = []
+ domain_url = EduSetting.get('host_name')
+ if projects.present?
+ projects.each do |p|
+ project_url = "/#{p.owner.login}/#{p.identifier}"
+ pj = {
+ id: p.id,
+ name: p.name,
+ is_public: p.is_public,
+ updated_on: p.updated_on.strftime("%Y-%m-%d"),
+ status: p.status,
+ is_member: p.member?(current_user.try(:id)),
+ owner: {
+ name: p.owner.try(:show_real_name),
+ login: p.owner.login
+ },
+ members_count: p&.members.size,
+ issues_count: p.issues_count - p.pull_requests_count,
+ commits_count: p&.project_score&.changeset_num.to_i,
+ http_url: domain_url + project_url,
+ http_collaborator_url: domain_url + project_url + "/setting/collaborator",
+ http_issues_url: domain_url + project_url + "/issues",
+ http_commits_url: domain_url + project_url + "/commits",
+ project_score: p&.project_score.present? ? p&.project_score&.as_json(:except=>[:created_at, :updated_at]).merge!(commit_time: format_time(p&.project_score&.commit_time)) : {}
+ }
+ projects_json.push(pj)
+ end
+ end
+ Rails.logger.info("==========projects_json========+########{projects_json}")
+ render json: { projects: projects_json.present? ? projects_json : {} }
+ end
+
+ def trustie_projects
+ user_id = User.select(:id, :login).where(login: params[:login])&.first&.id
+ projects = Project.visible
+
+ projects = projects.joins(:members).where(members: { user_id: user_id })
+
+ search = params[:search].to_s.strip
+ projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present?
+
+ projects = projects.select(:id, :name).limit(10).as_json
+ render json: { projects: projects }
+ end
+
+ def projects
+ is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
+ scope = Projects::ListMyQuery.call(params, @user,is_current_admin_user)
+ @total_count = scope.size
+ @projects = paginate(scope)
+ end
+
+ # TODO 其他平台登录时同步修改gitea平台对应用户的密码
+ # 该方法主要用于:别的平台初次部署对接forge平台,同步用户后,gitea平台对应的用户密码与forge平台用户密码不一致是问题
+ def sync_gitea_pwd
+ return render_error("未找到相关的用户") if @user.blank?
+
+ flag = sync_pwd_to_gitea!(@user, {password: params[:password].to_s})
+ flag ? render_ok : render_error('同步失败!')
+ end
+
+ # TODO
+ # 同步trusite平台用户的salt信息,只需同步一次,同步完成后,该方法可以删除
+ def sync_salt
+ user = User.find_by_login params[:login]
+ return if user.blank?
+ user.update_column(:salt, params[:salt])
+ render_ok
+ end
+
+ def sync_user_info
+ user = User.find_by_login params[:login]
+ return render_forbidden unless user === current_user
+
+ sync_params = {
+ email: params[:email],
+ password: params[:password]
+ }
+
+ Users::UpdateInfoForm.new(sync_params.merge(login: params[:login])).validate!
+
+ interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params)
+ if interactor.success?
+ user.update!(password: params[:password], mail: params[:email], status: User::STATUS_ACTIVE)
+ render_ok
+ else
+ render_error(interactor.error)
+ end
+ end
+
+ def email_search
+ return render_error('请输入email') if params[:email].blank?
+ @user = User.find_by(mail: params[:email])
+ end
+
+ private
+ def load_user
+ @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id])
+ end
+
+ def user_params
+ params.require(:user).permit(:nickname, :image,
+ user_extension_attributes: [
+ :gender, :location, :location_city,
+ :occupation, :technical_title,
+ :school_id, :department_id, :province, :city,
+ :custom_department, :identity, :student_id, :description,
+ :show_super_description, :super_description,
+ :show_email, :show_location, :show_department]
+ )
+ end
+
+ def reply_message_params
+ normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? ||
+ params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil?
+ params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id)
+ end
+
+ def check_user_exist
+ return if @user.present?
+ render_not_found
+ end
+
+ def sso_login
+ if params[:login].present? && !current_user.logged? && params[:websiteName].present?
+ user = User.where("login = ?", "#{params[:login].presence}").first
+ # 已同步注册,直接登录
+ if user.present?
+ successful_authentication(user)
+ else
+ autologin_register_by_educoder(params[:login].presence)
+ end
+ end
+ end
+
+ # 通过login参数查询头歌账号信息,注册并登录
+ def autologin_register_by_educoder(edu_login)
+ req_params = { "login" => "#{edu_login}", "private_token" => "hriEn3UwXfJs3PmyXnSH" }
+ 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"]
+ Rails.logger.info("user_info====== #{user_info}")
+ login = user_info["login"]
+ email = user_info["mail"]
+ phone = user_info["phone"]
+ real_name = user_info["username"]
+ department_name = user_info["school"]
+ password = "12345678"
+
+ # 没有用户时,新建用户并登录
+ user = User.where("login = ? or phone = ? or mail = ? ", "#{login}", phone, email).first
+ if user.present?
+ # 手机号先记录,后续用
+ user.update_column(:phone, "#{phone}") if phone.present?
+ else
+ ActiveRecord::Base.transaction do
+ email = "#{login}@gitlink.org.cn" if email.blank?
+ user_params = { status: 1, type: 'User', login: "#{login}", lastname: "#{real_name}", mail: "#{email}",
+ nickname: "#{real_name}", professional_certification: 0, certification: 0, grade: 0,
+ password: "#{password}", phone: "#{phone}", profile_completed: 1 }
+ user = User.create!(user_params)
+ UserExtension.create!(user_id: user.id, gender: 1, custom_department: "#{department_name}")
+ interactor = Gitea::RegisterInteractor.call({username: login, email: email, password: password})
+ if interactor.success?
+ gitea_user = interactor.result
+ Rails.logger.info("Gitea::RegisterInteractor.call result====== #{gitea_user}")
+ result = Gitea::User::GenerateTokenService.call(login, password)
+ user.gitea_token = result['sha1']
+ user.gitea_uid = gitea_user[:body]['id']
+ user.save!
+ else
+ Rails.logger.info("Gitea::RegisterInteractor.call error====== #{interactor.error}")
+ end
+ end
+ end
+ successful_authentication(user) if user.present?
+ end
+
+end
diff --git a/app/docs/slate/source/api.html.md b/app/docs/slate/source/api.html.md
index a846317c4..0031a99f4 100644
--- a/app/docs/slate/source/api.html.md
+++ b/app/docs/slate/source/api.html.md
@@ -16,6 +16,7 @@ includes:
- users
- projects
- repositories
+ - traces
- pulls
- issues
- organizations
diff --git a/app/docs/slate/source/includes/_projects.md b/app/docs/slate/source/includes/_projects.md
index d4899a710..d29d200b9 100644
--- a/app/docs/slate/source/includes/_projects.md
+++ b/app/docs/slate/source/includes/_projects.md
@@ -280,7 +280,7 @@ repo |是| |string |项目标识identifier
### 返回字段说明
参数 | 类型 | 字段说明
--------- | ----------- | -----------
-menu_name |string|导航名称, home:主页,code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,activity:动态,setting:仓库设置
+menu_name |string|导航名称, home:主页,code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,wiki:维基,services:服务,activity:动态,setting:仓库设置
> 返回的JSON示例:
@@ -408,7 +408,7 @@ await octokit.request('POST /api/yystopf/ceshi/project_units')
### 请求参数
参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ----------
-|unit_types |是| |array | 项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑 |
+|unit_types |是| |array | 项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,wiki:维基,resources:资源库,services:服务 |
### 返回字段说明:
@@ -849,4 +849,36 @@ await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.j
"created_at": "2021-04-26 09:54",
"time_ago": "1分钟前"
}
+```
+
+## 退出项目
+供项目成员(开发者、报告者)退出项目用
+
+> 示例:
+
+```shell
+curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json
+```
+
+```javascript
+await octokit.request('POST /api/:owner/:repo/quit.json')
+```
+
+### HTTP 请求
+`POST /api/:owner/:repo/quit.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+|owner |是| |string |用户登录名 |
+|repo |是| |string |项目标识identifier |
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
```
\ No newline at end of file
diff --git a/app/docs/slate/source/includes/_traces.md b/app/docs/slate/source/includes/_traces.md
new file mode 100644
index 000000000..e6aa24b48
--- /dev/null
+++ b/app/docs/slate/source/includes/_traces.md
@@ -0,0 +1,217 @@
+# Traces
+
+## 代码溯源初始化
+用户同意协议后请求的接口,创建代码溯源的账号
+
+> 示例:
+
+```shell
+curl -X POST \
+http://localhost:3000/api/traces/trace_users.json
+```
+
+```javascript
+await octokit.request('POST /api/traces/trace_users.json')
+```
+
+### HTTP 请求
+`POST api/traces/trace_users.json`
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
+```
+
+
+## 代码分析结果列表
+查询项目下代码分析的结果
+
+> 示例:
+
+```shell
+curl -X GET \
+http://localhost:3000/api/traces/yystopf/many_branch/task_results.json
+```
+
+```javascript
+await octokit.request('GET /api/traces/:owner/:repo/task_results.json')
+```
+
+### HTTP 请求
+`GET api/traces/:owner/:repo/task_results.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+owner|是|否|string | 项目所有者标识|
+repo|是 | 否|string | 项目标识 |
+page |否| 1 | int | 页码 |
+limit |否| 15 | int | 每页数量 |
+
+### 返回字段说明(暂缺)
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "data": [
+ {
+ "accuracy": "20",
+ "code_type": "C",
+ "depth": "2",
+ "detect_flag": "快速-组件级",
+ "detect_rule": "快速-组件级,2,20,,开源软件,50,10",
+ "detect_startdate": "2022-05-10 15:59:46 ",
+ "detect_status": "fail",
+ "detectflag": "快速-组件级",
+ "fail_reason": "Invalid package type",
+ "file_name": "many_branch.zip",
+ "license_process": "100",
+ "licenseparam": "开源软件",
+ "package_type": "",
+ "product_name": "84727546110",
+ "project_id": "6dbc3e42-5857-4ca4-a54d-58fd9dbf6dc5",
+ "sim_process": "100",
+ "similarity_process": "2",
+ "task_id": "15139171-091b-4316-98b1-6068970efa44",
+ "totalsize": 5,
+ "uid": "78",
+ "vuln_process": "",
+ "vulnlevel": ""
+ }
+ ]
+}
+```
+
+
+
+
+## 新建分析
+用户选择仓库分支进行代码分析的接口
+
+> 示例:
+
+```shell
+curl -X POST \
+http://localhost:3000/api/traces/yystopf/many_branch/tasks.json
+```
+
+```javascript
+await octokit.request('POST /api/traces/:owner/:repo/tasks.json')
+```
+
+### HTTP 请求
+`POST api/traces/:owner/:repo/tasks.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+owner |是 | 否 | string | 项目所有者标识 |
+repo |是 | 否 | string | 项目标识 |
+branch_name|是 | 否| string | 分支名称 |
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
+```
+
+
+## 重新扫描
+对代码分析结果进行再次分析
+
+> 示例:
+
+```shell
+curl -X GET \
+http://localhost:3000/api/traces/yystopf/many_branch/reload_task.json
+```
+
+```javascript
+await octokit.request('GET /api/traces/:owner/:repo/reload_task.json')
+```
+
+### HTTP 请求
+`GET api/traces/:owner/:repo/reload_task.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+owner |是 | 否 | string | 项目所有者标识 |
+repo |是 | 否 | string | 项目标识 |
+project_id|是 | 否| string | 代码分析结果里的project_id |
+branch_name|是 | 否| string | 分支名称 |
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
+```
+
+
+
+## 下载报告
+把代码分析的结果下载到本地
+
+> 示例:
+
+```shell
+curl -X GET \
+http://localhost:3000/api/traces/yystopf/many_branch/task_pdf.json
+```
+
+```javascript
+await octokit.request('GET /api/traces/:owner/:repo/task_pdf.json')
+```
+
+### HTTP 请求
+`GET api/traces/:owner/:repo/task_pdf.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+owner |是 | 否 | string | 项目所有者标识 |
+repo |是 | 否 | string | 项目标识 |
+task_id|是 | 否| string | 代码分析结果里的task_id |
+
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
+```
+
diff --git a/app/forms/gitea/user/update_form.rb b/app/forms/gitea/user/update_form.rb
index 7912d51fa..401282ff1 100644
--- a/app/forms/gitea/user/update_form.rb
+++ b/app/forms/gitea/user/update_form.rb
@@ -4,7 +4,7 @@ class Gitea::User::UpdateForm
attr_accessor :username, :email, :admin, :allow_create_organization, :allow_git_hook, :allow_import_local,
:full_name, :location, :login_name, :max_repo_creation, :must_change_password, :password, :prohibit_login,
- :source_id, :website
+ :source_id, :website, :new_name
validates :username, presence: true
validates :email, presence: true, format: { with: EMAIL_REGEX, multiline: true }
diff --git a/app/forms/register/base_form.rb b/app/forms/register/base_form.rb
index 150fef73a..8824fc23c 100644
--- a/app/forms/register/base_form.rb
+++ b/app/forms/register/base_form.rb
@@ -3,28 +3,40 @@ module Register
include ActiveModel::Model
private
- def check_login(login)
+ def check_login(login, user=nil)
login = strip(login)
raise LoginError, "登录名格式有误" unless login =~ CustomRegexp::LOGIN
login_exist = Owner.exists?(login: login) || ReversedKeyword.check_exists?(login)
- raise LoginError, '登录名已被使用' if login_exist
+ if user.present?
+ raise LoginError, '登录名已被使用' if login_exist && login != user&.login
+ else
+ raise LoginError, '登录名已被使用' if login_exist
+ end
end
- def check_mail(mail)
+ def check_mail(mail, user=nil)
mail = strip(mail)
raise EmailError, "邮件格式有误" unless mail =~ CustomRegexp::EMAIL
mail_exist = Owner.exists?(mail: mail)
- raise EmailError, '邮箱已被使用' if mail_exist
+ if user.present?
+ raise EmailError, '邮箱已被使用' if mail_exist && mail != user&.mail
+ else
+ raise EmailError, '邮箱已被使用' if mail_exist
+ end
end
- def check_phone(phone)
+ def check_phone(phone, user=nil)
phone = strip(phone)
raise PhoneError, "手机号格式有误" unless phone =~ CustomRegexp::PHONE
phone_exist = Owner.exists?(phone: phone)
- raise PhoneError, '手机号已被使用' if phone_exist
+ if user.present?
+ raise PhoneError, '手机号已被使用' if phone_exist && phone != user&.phone
+ else
+ raise PhoneError, '手机号已被使用' if phone_exist
+ end
end
end
end
diff --git a/app/forms/register/login_check_columns_form.rb b/app/forms/register/login_check_columns_form.rb
new file mode 100644
index 000000000..0c6a93af3
--- /dev/null
+++ b/app/forms/register/login_check_columns_form.rb
@@ -0,0 +1,19 @@
+module Register
+ class LoginCheckColumnsForm < Register::BaseForm
+ attr_accessor :type, :value, :user
+
+ validates :type, presence: true, numericality: true
+ validates :value, presence: true
+ validate :check!
+
+ def check!
+ # params[:type] 为事件类型 1:登录名(login) 2:email(邮箱) 3:phone(手机号)
+ case strip(type).to_i
+ when 1 then check_login(strip(value), user)
+ when 2 then check_mail(strip(value), user)
+ when 3 then check_phone(strip(value), user)
+ else raise("type值无效")
+ end
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c9096fe22..25b642d73 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,486 +1,486 @@
-# 所有的方法请按首字母的顺序依次列出
-module ApplicationHelper
- include Gitlink::I18n
- include GitHelper
-
- ONE_MINUTE = 60 * 1000
- ONE_HOUR = 60 * ONE_MINUTE
- ONE_DAY = 24 * ONE_HOUR
- ONE_MONTH = 30 * ONE_DAY
-
- ONE_YEAR = 12 * ONE_MONTH
-
- # 全局参数配置
- def edu_setting name
- EduSetting.get(name)
- end
-
- # xss共计问题
- def content_safe content
- tags = %w(
- a abbr b bdo blockquote br caption cite code col colgroup dd del dfn dl
- dt em figcaption figure h1 h2 h3 h4 h5 h6 hgroup i img ins kbd li mark
- ol p pre q rp rt ruby s samp small strike strong sub sup table tbody td
- tfoot th thead time tr u ul var wbr div span
- )
- attributes = %w(href src width height alt cite datetime title class name xml:lang abbr style)
- sanitize content, tags: tags, attributes: attributes
- end
-
- def graduation_navigation graduation
- graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务"
- end
-
- def graduation_navigation_id course
- course.course_modules.find_by(module_type: "graduation").try(:id)
- end
-
- # git用户
- # git用户命名规则:login+"@educoder.net"
- def git_username(email)
- User.find_by_mail(email) || User.find_by_login(email.split("@").first)
- end
-
- # 不同的类型扩展不同的目录
- def relative_path
- "avatars"
- end
-
- def replace_bytes_to_b(size_string)
- return size_string.gsub("Bytes", "B").gsub("bytes", "B").gsub("字节", "B")
- end
-
- def storage_path
- File.join(Rails.root, "public", "images", relative_path)
- end
-
- # 推荐实训
- def recommend_shixun(shixun)
- tag_repertoire_id = shixun.tag_repertoires.first.present? ? shixun.tag_repertoires.first.try(:id) : 0
- shixun_id = ShixunTagRepertoire.where("tag_repertoire_id = #{tag_repertoire_id} and
- shixun_id != #{shixun.id}").pluck(:shixun_id)
-
- shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",")
- Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id
- in(#{shixun_id})").unhidden.publiced.order("homepage_show asc, myshixuns_count desc").limit(3)
-
- end
-
- # shixun开启挑战对应的行为名及url
- def task_operation_url current_myshixun, shixun
- if current_myshixun.blank?
- name = shixun.status == 0 ? "模拟实战" : "开启挑战"
- url = "/shixuns/#{shixun.identifier}/shixun_exec"
- else
- identifier = current_myshixun.current_task(current_myshixun.games).try(:identifier)
- if current_myshixun.status == 1
- name = "查看实战"
- else
- name = "继续挑战"
- end
- url = identifier
- end
- [name, url]
- end
-
- # 获取当前时间
- def time_from_now(time)
- if String === time
- time = Time.parse(time)
- end
-
- lastUpdateTime = time.to_i*1000
-
- currentTime = Time.now.to_i*1000
- timePassed = currentTime - lastUpdateTime
- timeIntoFormat = 0
- updateAtValue = ""
- if timePassed < 0
- updateAtValue = "刚刚"
- elsif timePassed < ONE_MINUTE
- updateAtValue = "1分钟前"
- elsif timePassed < ONE_HOUR
- timeIntoFormat = timePassed / ONE_MINUTE
- updateAtValue = timeIntoFormat.to_s + "分钟前"
- elsif (timePassed < ONE_DAY)
- timeIntoFormat = timePassed / ONE_HOUR
- updateAtValue = timeIntoFormat.to_s + "小时前"
- elsif (timePassed < ONE_MONTH)
- timeIntoFormat = timePassed / ONE_DAY
- updateAtValue = timeIntoFormat.to_s + "天前"
- elsif (timePassed < ONE_YEAR)
- timeIntoFormat = timePassed / ONE_MONTH
- updateAtValue = timeIntoFormat.to_s + "个月前"
- else
- timeIntoFormat = timePassed / ONE_YEAR
- updateAtValue = timeIntoFormat.to_s + "年前"
- end
- updateAtValue
- end
-
- # 计算到结束还有多长时间 **天**小时**分
- def how_much_time(time)
- if time.nil? || time < Time.now #6.21 -hs 增加小于time.now
- ''
- else
- result = ((time - Time.now.to_i).to_i / (24*60*60)).to_s + " 天 "
- result += (((time - Time.now.to_i).to_i % (24*60*60)) / (60*60)).to_s + " 小时 "
- result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + " 分 "
- end
- end
-
- def format_time(time)
- time.present? ? time.strftime("%Y-%m-%d %H:%M") : ''
- end
-
- # 用户图像url,如果不存在的话,source为匿名用户,即默认使用匿名用户图像
- def url_to_avatar(source)
- if File.exist?(disk_filename(source&.class, source&.id))
- ctime = File.ctime(disk_filename(source.class, source.id)).to_i
- if %w(User Organization).include?(source.class.to_s)
- File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
- else
- File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
- end
- elsif source.class.to_s == 'User'
- source.get_letter_avatar_url
- end
- end
-
- def url_to_avatar_with_platform_url(source)
- platform_url = Rails.application.config_for(:configuration)['platform_url']
- if platform_url
- return Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(source).to_s
- else
- return url_to_avatar(source).to_s
- end
- end
-
- # 主页banner图
- def banner_img(source_type)
- if File.exist?(disk_filename(source_type, "banner"))
- ctime = File.ctime(disk_filename(source_type, "banner")).to_i
- File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}"
- end
- end
-
- def disk_filename(source_type,source_id,image_file=nil)
- File.join(storage_path, "#{source_type}", "#{source_id}")
- end
-
- def disk_auth_filename(source_type, source_id, type)
- File.join(storage_path, "#{source_type}", "#{source_id}#{type}")
- end
-
- def disk_real_name_auth_filename(source_id)
- disk_auth_filename('UserAuthentication', source_id, 'ID')
- end
-
- def auth_file_url(source_type, source_id, type)
- File.join('/images', relative_path, source_type, "#{source_id}#{type}")
- end
-
- def real_name_auth_file_url(source_id)
- auth_file_url('UserAuthentication', source_id, 'ID')
- end
-
- def disk_professional_auth_filename(source_id)
- disk_auth_filename('UserAuthentication', source_id, 'PRO')
- end
-
- def professional_auth_file_url(source_id)
- auth_file_url('UserAuthentication', source_id, 'PRO')
- end
-
- def shixun_url_to_avatar(shixun)
- if File.exist?(disk_filename(shixun.class, shixun.id))
- File.join("images/#{relative_path}", "#{shixun.class}", "#{shixun.id}")
- else
- File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg")
- end
- end
-
- # 选用实训的学校情况
- def school_user_detail shixun
- user_ids = shixun.myshixuns.map(&:user_id).uniq # 走缓存取数据
- school_ids = UserExtension.where(user_id:user_ids).pluck(:school_id).uniq
- school_names = School.where(id: school_ids[0..1]).pluck(:name)
- school_size = school_ids.size
- str = school_size > 0 ? "#{school_names.join("、")}等 #{school_size}所" : "0所"
- end
-
- # 普通/分组 作业作品状态数组
- def student_work_status homework, user_id, course, work
- status = []
- homework_setting = homework.homework_group_setting user_id, true
- work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id)
- late_time = homework.late_time || course.end_date
-
- if course.is_end && work && work.work_status > 0
- status << "查看作品"
- elsif !course.is_end
- if homework_setting.publish_time && homework_setting.publish_time < Time.now
- # 作业未截止时
- if homework_setting.end_time > Time.now
- if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
- if work.project_id.nil? || work.project_id == 0
- status << "创建项目"
- status << "关联项目"
- elsif work.work_status == 0
- status << "取消关联"
- status << "提交作品"
- else
- status << "修改作品"
- end
- else
- if work.work_status == 0
- status << "提交作品"
- else
- status << "修改作品"
- end
- end
-
- # 补交阶段
- elsif homework.allow_late && (late_time.nil? || late_time > Time.now)
- if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
- if work.project_id.nil? || work.project_id == 0
- status << "创建项目"
- status << "关联项目"
- elsif work.work_status == 0
- status << "取消关联"
- status << "补交作品"
- else
- status << "补交附件"
- status << "查看作品"
- end
- else
- if work.work_status == 0
- status << "补交作品"
- else
- status << "补交附件"
- status << "查看作品"
- end
- end
-
- # 匿评阶段
- elsif work.work_status != 0
- if homework.homework_detail_manual.comment_status == 3
- work_ids = homework.student_works.has_committed.pluck(:id)
- if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0
- status << "匿评作品"
- end
- end
- status << "查看作品"
- end
- end
- end
- end
-
- def commit_des_status work, homework
- status = []
- homework_setting = homework.homework_group_setting work.user_id
-
- # 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结
- if homework_setting.publish_time && homework_setting.publish_time < Time.now && work.work_status > 0 &&
- ((homework_setting.end_time && homework_setting.end_time > Time.now) ||
- (homework.allow_late && homework.late_time && homework.late_time > Time.now))
- work.description.present? ? status << "修改总结" : status << "提交总结"
- end
- end
-
- def download_url attachment,options={}
- attachment_path(attachment,options)
- end
-
- # 耗时:天、小时、分、秒
- # 小于1分钟则不显示
- def game_spend_time time
- day = time / 86400
- hour = time % (24*60*60) / (60*60)
- min = time % (24*60*60) % (60*60) / 60
- sec = time % (24*60*60) % (60*60) % 60
- if day < 1
- if hour < 1
- if min < 1
- if sec < 1
- time = "--"
- else
- time = "#{sec}秒"
- end
- else
- time = "#{min}分钟 #{sec}秒"
- end
- else
- time = "#{hour}小时 #{min}分钟 #{sec}秒"
- end
- else
- time = "#{day}天 #{hour}小时 #{min}分钟 #{sec}秒"
- end
- return time
- end
-
- def absolute_path(file_path)
- file_root_directory + File.join(edu_setting('attachment_folder'), file_path)
- end
-
- def file_root_directory
- Rails.root.to_s
- end
-
- def file_storage_directory
- file_root_directory + edu_setting('attachment_folder')
- end
-
- def local_path(file)
- File.join(file.disk_directory, file.disk_filename)
- end
-
- def update_downloads(file)
- file.update_attributes(:downloads => file.downloads + 1)
- end
-
- # 项目信息,
- def project_info work, current_user, course_identity
- project = work.project
- if project
- if project.status == 9
- {id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count}
- else
- project_score = project.project_score
- if project.is_public || current_user.manager_of_project?(project) || course_identity < Course::STUDENT
- {id: project.id, name: project.name, author: project.creator, member_count: project.project_members.count,
- all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score,
- attachment_score: project_score.attachment_score, message_score: project_score.message_score}
- else
- {id: -1, name: "#{project.name}", title: "该项目是私有的", author: project.creator, member_count: project.project_members.count,
- all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score,
- attachment_score: project_score.attachment_score, message_score: project_score.message_score}
- end
- end
- else
- {id: -1, name: "--", title: "--"}
- end
- end
-
- def message_content(content)
- content = (strip_html content).strip
- content = content.gsub(/\s+/, " ")
- if content.gsub(" ", "") == ""
- content = "[非文本消息]"
- end
- content
- end
-
- def strip_html(text, len=0, endss="...")
- ss = ""
- if !text.nil? && text.length>0
- ss=text.gsub(/<\/?.*?>/, '').strip
- ss = ss.gsub(/ */, '')
- ss = ss.gsub(/\r\n/,'') #新增
- ss = ss.gsub(/\n/,'') #新增
- if len > 0 && ss.length > len
- ss = ss[0, len] + endss
- elsif len > 0 && ss.length <= len
- ss = ss
- #ss = truncate(ss, :length => len)
- end
- end
- ss
- end
-
- def strip_export_title(content)
- con_ = ""
- if content.length > 0
- con_ = strip_tags(content)
- con_ = con_.gsub(/\r\n/,'').gsub(/ */, '').strip
- end
- con_
- end
-
- def pdf_load_sources(*arg)
- arr = arg.map do |path|
- content_tag(:script) do
- File.open(Rails.root.join('public', path)).read.to_s.html_safe
- end
- end
-
- raw arr.join('')
- end
-
-
-
- # 导出pdf时,转化markdown为html
- def to_markdown(text,origin_url)
- return nil if text.blank?
- options = {
- :autolink => true,
- :no_intra_emphasis => true,
- :fenced_code_blocks => true,
- :lax_html_blocks => true,
- :strikethrough => true,
- :superscript => false,
- :tables => true
- }
- markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options)
- m_t = markdown.render(text)
- m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t
- end
-
- def shixun_status_class(shixun)
- case shixun.status
- when 0 then 'text-info'
- when 1 then 'text-warning'
- when 2 then 'text-success'
- when 3 then 'text-secondary'
- end
- end
-
- def render_unix_time(date)
- date.to_time.to_i
- end
-
- def find_user_by_login(login)
- User.find_by_login login
- end
-
- def find_user_by_login_and_mail(login, mail)
- User.find_by(login: login, mail: mail)
- end
-
- def find_user_by_gitea_uid(gitea_uid)
- User.find_by(gitea_uid: gitea_uid)
- end
-
- def find_user_in_redis_cache(login, email)
- $redis_cache.hgetall("v2-owner-common:#{login}-#{email}")
- end
-
- def find_user_in_redis_cache_by_id(id)
- $redis_cache.hgetall("v2-owner-common:#{id}")
- end
-
- def render_base64_decoded(str)
- return nil if str.blank?
- Base64.decode64 str
- end
-
- def render_admin_statistics_item
- url = Rails.application.config_for(:configuration)["admin_statistics_url"]
-
- return if url.blank?
- content_tag(:li) do
- sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root')
- end
- end
-
- # 1 手机类型;0 邮箱类型
- # 注意新版的login是自动名生成的
- def phone_mail_type value
- value =~ /^1\d{10}$/ ? 1 : 0
- end
-
- def strip(str)
- str.to_s.strip.presence
- end
-
-end
+# 所有的方法请按首字母的顺序依次列出
+module ApplicationHelper
+ include Gitlink::I18n
+ include GitHelper
+
+ ONE_MINUTE = 60 * 1000
+ ONE_HOUR = 60 * ONE_MINUTE
+ ONE_DAY = 24 * ONE_HOUR
+ ONE_MONTH = 30 * ONE_DAY
+
+ ONE_YEAR = 12 * ONE_MONTH
+
+ # 全局参数配置
+ def edu_setting name
+ EduSetting.get(name)
+ end
+
+ # xss共计问题
+ def content_safe content
+ tags = %w(
+ a abbr b bdo blockquote br caption cite code col colgroup dd del dfn dl
+ dt em figcaption figure h1 h2 h3 h4 h5 h6 hgroup i img ins kbd li mark
+ ol p pre q rp rt ruby s samp small strike strong sub sup table tbody td
+ tfoot th thead time tr u ul var wbr div span
+ )
+ attributes = %w(href src width height alt cite datetime title class name xml:lang abbr style)
+ sanitize content, tags: tags, attributes: attributes
+ end
+
+ def graduation_navigation graduation
+ graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务"
+ end
+
+ def graduation_navigation_id course
+ course.course_modules.find_by(module_type: "graduation").try(:id)
+ end
+
+ # git用户
+ # git用户命名规则:login+"@educoder.net"
+ def git_username(email)
+ User.find_by_mail(email) || User.find_by_login(email.split("@").first)
+ end
+
+ # 不同的类型扩展不同的目录
+ def relative_path
+ "avatars"
+ end
+
+ def replace_bytes_to_b(size_string)
+ return size_string.gsub("Bytes", "B").gsub("bytes", "B").gsub("字节", "B")
+ end
+
+ def storage_path
+ File.join(Rails.root, "public", "images", relative_path)
+ end
+
+ # 推荐实训
+ def recommend_shixun(shixun)
+ tag_repertoire_id = shixun.tag_repertoires.first.present? ? shixun.tag_repertoires.first.try(:id) : 0
+ shixun_id = ShixunTagRepertoire.where("tag_repertoire_id = #{tag_repertoire_id} and
+ shixun_id != #{shixun.id}").pluck(:shixun_id)
+
+ shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",")
+ Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id
+ in(#{shixun_id})").unhidden.publiced.order("homepage_show asc, myshixuns_count desc").limit(3)
+
+ end
+
+ # shixun开启挑战对应的行为名及url
+ def task_operation_url current_myshixun, shixun
+ if current_myshixun.blank?
+ name = shixun.status == 0 ? "模拟实战" : "开启挑战"
+ url = "/shixuns/#{shixun.identifier}/shixun_exec"
+ else
+ identifier = current_myshixun.current_task(current_myshixun.games).try(:identifier)
+ if current_myshixun.status == 1
+ name = "查看实战"
+ else
+ name = "继续挑战"
+ end
+ url = identifier
+ end
+ [name, url]
+ end
+
+ # 获取当前时间
+ def time_from_now(time)
+ if String === time
+ time = Time.parse(time)
+ end
+
+ lastUpdateTime = time.to_i*1000
+
+ currentTime = Time.now.to_i*1000
+ timePassed = currentTime - lastUpdateTime
+ timeIntoFormat = 0
+ updateAtValue = ""
+ if timePassed < 0
+ updateAtValue = "刚刚"
+ elsif timePassed < ONE_MINUTE
+ updateAtValue = "1分钟前"
+ elsif timePassed < ONE_HOUR
+ timeIntoFormat = timePassed / ONE_MINUTE
+ updateAtValue = timeIntoFormat.to_s + "分钟前"
+ elsif (timePassed < ONE_DAY)
+ timeIntoFormat = (timePassed.to_f / ONE_HOUR).ceil
+ updateAtValue = timeIntoFormat.to_s + "小时前"
+ elsif (timePassed < ONE_MONTH)
+ timeIntoFormat = (timePassed.to_f / ONE_DAY).ceil
+ updateAtValue = timeIntoFormat.to_s + "天前"
+ elsif (timePassed < ONE_YEAR)
+ timeIntoFormat = (timePassed.to_f / ONE_MONTH).ceil
+ updateAtValue = timeIntoFormat.to_s + "个月前"
+ else
+ timeIntoFormat = timePassed / ONE_YEAR
+ updateAtValue = timeIntoFormat.to_s + "年前"
+ end
+ updateAtValue
+ end
+
+ # 计算到结束还有多长时间 **天**小时**分
+ def how_much_time(time)
+ if time.nil? || time < Time.now #6.21 -hs 增加小于time.now
+ ''
+ else
+ result = ((time - Time.now.to_i).to_i / (24*60*60)).to_s + " 天 "
+ result += (((time - Time.now.to_i).to_i % (24*60*60)) / (60*60)).to_s + " 小时 "
+ result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + " 分 "
+ end
+ end
+
+ def format_time(time)
+ time.present? ? time.strftime("%Y-%m-%d %H:%M") : ''
+ end
+
+ # 用户图像url,如果不存在的话,source为匿名用户,即默认使用匿名用户图像
+ def url_to_avatar(source)
+ if File.exist?(disk_filename(source&.class, source&.id))
+ ctime = File.ctime(disk_filename(source.class, source.id)).to_i
+ if %w(User Organization).include?(source.class.to_s)
+ File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
+ else
+ File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
+ end
+ elsif source.class.to_s == 'User'
+ source.get_letter_avatar_url
+ end
+ end
+
+ def url_to_avatar_with_platform_url(source)
+ platform_url = Rails.application.config_for(:configuration)['platform_url']
+ if platform_url
+ return Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(source).to_s
+ else
+ return url_to_avatar(source).to_s
+ end
+ end
+
+ # 主页banner图
+ def banner_img(source_type)
+ if File.exist?(disk_filename(source_type, "banner"))
+ ctime = File.ctime(disk_filename(source_type, "banner")).to_i
+ File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}"
+ end
+ end
+
+ def disk_filename(source_type,source_id,image_file=nil)
+ File.join(storage_path, "#{source_type}", "#{source_id}")
+ end
+
+ def disk_auth_filename(source_type, source_id, type)
+ File.join(storage_path, "#{source_type}", "#{source_id}#{type}")
+ end
+
+ def disk_real_name_auth_filename(source_id)
+ disk_auth_filename('UserAuthentication', source_id, 'ID')
+ end
+
+ def auth_file_url(source_type, source_id, type)
+ File.join('/images', relative_path, source_type, "#{source_id}#{type}")
+ end
+
+ def real_name_auth_file_url(source_id)
+ auth_file_url('UserAuthentication', source_id, 'ID')
+ end
+
+ def disk_professional_auth_filename(source_id)
+ disk_auth_filename('UserAuthentication', source_id, 'PRO')
+ end
+
+ def professional_auth_file_url(source_id)
+ auth_file_url('UserAuthentication', source_id, 'PRO')
+ end
+
+ def shixun_url_to_avatar(shixun)
+ if File.exist?(disk_filename(shixun.class, shixun.id))
+ File.join("images/#{relative_path}", "#{shixun.class}", "#{shixun.id}")
+ else
+ File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg")
+ end
+ end
+
+ # 选用实训的学校情况
+ def school_user_detail shixun
+ user_ids = shixun.myshixuns.map(&:user_id).uniq # 走缓存取数据
+ school_ids = UserExtension.where(user_id:user_ids).pluck(:school_id).uniq
+ school_names = School.where(id: school_ids[0..1]).pluck(:name)
+ school_size = school_ids.size
+ str = school_size > 0 ? "#{school_names.join("、")}等 #{school_size}所" : "0所"
+ end
+
+ # 普通/分组 作业作品状态数组
+ def student_work_status homework, user_id, course, work
+ status = []
+ homework_setting = homework.homework_group_setting user_id, true
+ work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id)
+ late_time = homework.late_time || course.end_date
+
+ if course.is_end && work && work.work_status > 0
+ status << "查看作品"
+ elsif !course.is_end
+ if homework_setting.publish_time && homework_setting.publish_time < Time.now
+ # 作业未截止时
+ if homework_setting.end_time > Time.now
+ if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
+ if work.project_id.nil? || work.project_id == 0
+ status << "创建项目"
+ status << "关联项目"
+ elsif work.work_status == 0
+ status << "取消关联"
+ status << "提交作品"
+ else
+ status << "修改作品"
+ end
+ else
+ if work.work_status == 0
+ status << "提交作品"
+ else
+ status << "修改作品"
+ end
+ end
+
+ # 补交阶段
+ elsif homework.allow_late && (late_time.nil? || late_time > Time.now)
+ if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
+ if work.project_id.nil? || work.project_id == 0
+ status << "创建项目"
+ status << "关联项目"
+ elsif work.work_status == 0
+ status << "取消关联"
+ status << "补交作品"
+ else
+ status << "补交附件"
+ status << "查看作品"
+ end
+ else
+ if work.work_status == 0
+ status << "补交作品"
+ else
+ status << "补交附件"
+ status << "查看作品"
+ end
+ end
+
+ # 匿评阶段
+ elsif work.work_status != 0
+ if homework.homework_detail_manual.comment_status == 3
+ work_ids = homework.student_works.has_committed.pluck(:id)
+ if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0
+ status << "匿评作品"
+ end
+ end
+ status << "查看作品"
+ end
+ end
+ end
+ end
+
+ def commit_des_status work, homework
+ status = []
+ homework_setting = homework.homework_group_setting work.user_id
+
+ # 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结
+ if homework_setting.publish_time && homework_setting.publish_time < Time.now && work.work_status > 0 &&
+ ((homework_setting.end_time && homework_setting.end_time > Time.now) ||
+ (homework.allow_late && homework.late_time && homework.late_time > Time.now))
+ work.description.present? ? status << "修改总结" : status << "提交总结"
+ end
+ end
+
+ def download_url attachment,options={}
+ attachment_path(attachment,options)
+ end
+
+ # 耗时:天、小时、分、秒
+ # 小于1分钟则不显示
+ def game_spend_time time
+ day = time / 86400
+ hour = time % (24*60*60) / (60*60)
+ min = time % (24*60*60) % (60*60) / 60
+ sec = time % (24*60*60) % (60*60) % 60
+ if day < 1
+ if hour < 1
+ if min < 1
+ if sec < 1
+ time = "--"
+ else
+ time = "#{sec}秒"
+ end
+ else
+ time = "#{min}分钟 #{sec}秒"
+ end
+ else
+ time = "#{hour}小时 #{min}分钟 #{sec}秒"
+ end
+ else
+ time = "#{day}天 #{hour}小时 #{min}分钟 #{sec}秒"
+ end
+ return time
+ end
+
+ def absolute_path(file_path)
+ file_root_directory + File.join(edu_setting('attachment_folder'), file_path)
+ end
+
+ def file_root_directory
+ Rails.root.to_s
+ end
+
+ def file_storage_directory
+ file_root_directory + edu_setting('attachment_folder')
+ end
+
+ def local_path(file)
+ File.join(file.disk_directory, file.disk_filename)
+ end
+
+ def update_downloads(file)
+ file.update_attributes(:downloads => file.downloads + 1)
+ end
+
+ # 项目信息,
+ def project_info work, current_user, course_identity
+ project = work.project
+ if project
+ if project.status == 9
+ {id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count}
+ else
+ project_score = project.project_score
+ if project.is_public || current_user.manager_of_project?(project) || course_identity < Course::STUDENT
+ {id: project.id, name: project.name, author: project.creator, member_count: project.project_members.count,
+ all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score,
+ attachment_score: project_score.attachment_score, message_score: project_score.message_score}
+ else
+ {id: -1, name: "#{project.name}", title: "该项目是私有的", author: project.creator, member_count: project.project_members.count,
+ all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score,
+ attachment_score: project_score.attachment_score, message_score: project_score.message_score}
+ end
+ end
+ else
+ {id: -1, name: "--", title: "--"}
+ end
+ end
+
+ def message_content(content)
+ content = (strip_html content).strip
+ content = content.gsub(/\s+/, " ")
+ if content.gsub(" ", "") == ""
+ content = "[非文本消息]"
+ end
+ content
+ end
+
+ def strip_html(text, len=0, endss="...")
+ ss = ""
+ if !text.nil? && text.length>0
+ ss=text.gsub(/<\/?.*?>/, '').strip
+ ss = ss.gsub(/ */, '')
+ ss = ss.gsub(/\r\n/,'') #新增
+ ss = ss.gsub(/\n/,'') #新增
+ if len > 0 && ss.length > len
+ ss = ss[0, len] + endss
+ elsif len > 0 && ss.length <= len
+ ss = ss
+ #ss = truncate(ss, :length => len)
+ end
+ end
+ ss
+ end
+
+ def strip_export_title(content)
+ con_ = ""
+ if content.length > 0
+ con_ = strip_tags(content)
+ con_ = con_.gsub(/\r\n/,'').gsub(/ */, '').strip
+ end
+ con_
+ end
+
+ def pdf_load_sources(*arg)
+ arr = arg.map do |path|
+ content_tag(:script) do
+ File.open(Rails.root.join('public', path)).read.to_s.html_safe
+ end
+ end
+
+ raw arr.join('')
+ end
+
+
+
+ # 导出pdf时,转化markdown为html
+ def to_markdown(text,origin_url)
+ return nil if text.blank?
+ options = {
+ :autolink => true,
+ :no_intra_emphasis => true,
+ :fenced_code_blocks => true,
+ :lax_html_blocks => true,
+ :strikethrough => true,
+ :superscript => false,
+ :tables => true
+ }
+ markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options)
+ m_t = markdown.render(text)
+ m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t
+ end
+
+ def shixun_status_class(shixun)
+ case shixun.status
+ when 0 then 'text-info'
+ when 1 then 'text-warning'
+ when 2 then 'text-success'
+ when 3 then 'text-secondary'
+ end
+ end
+
+ def render_unix_time(date)
+ date.to_time.to_i
+ end
+
+ def find_user_by_login(login)
+ User.find_by_login login
+ end
+
+ def find_user_by_login_and_mail(login, mail)
+ User.find_by(login: login, mail: mail)
+ end
+
+ def find_user_by_gitea_uid(gitea_uid)
+ User.find_by(gitea_uid: gitea_uid)
+ end
+
+ def find_user_in_redis_cache(login, email)
+ $redis_cache.hgetall("v2-owner-common:#{login}-#{email}")
+ end
+
+ def find_user_in_redis_cache_by_id(id)
+ $redis_cache.hgetall("v2-owner-common:#{id}")
+ end
+
+ def render_base64_decoded(str)
+ return nil if str.blank?
+ Base64.decode64 str
+ end
+
+ def render_admin_statistics_item
+ url = Rails.application.config_for(:configuration)["admin_statistics_url"]
+
+ return if url.blank?
+ content_tag(:li) do
+ sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root')
+ end
+ end
+
+ # 1 手机类型;0 邮箱类型
+ # 注意新版的login是自动名生成的
+ def phone_mail_type value
+ value =~ /^1\d{10}$/ ? 1 : 0
+ end
+
+ def strip(str)
+ str.to_s.strip.presence
+ end
+
+end
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index 3af3ef140..1df8086ae 100644
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -1,169 +1,218 @@
-module RepositoriesHelper
- def render_permission(user, project)
- return "Admin" if user&.admin?
- project.get_premission(user)
- end
-
- def render_decode64_content(str)
- return nil if str.blank?
- Base64.decode64(str).force_encoding("UTF-8").encode("UTF-8", invalid: :replace)
- end
-
- def download_type(str)
- default_type = %w(xlsx xls ppt pptx pdf zip 7z rar exe pdb obj idb RData rdata doc docx mpp vsdx dot otf eot ttf woff woff2 mp4 mov wmv flv mpeg avi avchd webm mkv)
- default_type.include?(str&.downcase) || str.blank?
- end
-
- def image_type?(str)
- default_type = %w(png jpg gif tif psd svg bmp webp jpeg ico psd)
- default_type.include?(str&.downcase)
- end
-
- def is_readme?(type, str)
- return false if type != 'file' || str.blank?
- readme_types = ["readme.md", "readme", "readme_en.md", "readme_zh.md", "readme_en", "readme_zh"]
- readme_types.include?(str.to_s.downcase)
- end
-
- def render_commit_author(author_json)
- return nil if author_json.blank? || (author_json["id"].blank? && author_json['name'].blank?)
- if author_json["id"].present?
- return find_user_by_gitea_uid author_json['id']
- end
- if author_json["id"].nil? && (author_json["name"].present? && author_json["email"].present?)
- return find_user_by_login_and_mail(author_json['name'], author_json["email"])
- end
- end
-
- def render_cache_commit_author(author_json)
- if author_json["name"].present? && author_json["email"].present?
- return find_user_in_redis_cache(author_json['name'], author_json['email'])
- end
- if author_json["Name"].present? && author_json["Email"].present?
- return find_user_in_redis_cache(author_json['Name'], author_json['Email'])
- end
- end
-
- def readme_render_decode64_content(str, owner, repo, ref, path)
- return nil if str.blank?
- begin
- content = Base64.decode64(str).force_encoding('UTF-8')
-
- c_regex = /\!\[.*?\]\((.*?)\)/
- src_regex = /src=\"(.*?)\"/
- src2_regex = /src='(.*?)'/
- ss = content.to_s.scan(c_regex)
- ss_src = content.scan(src_regex)
- ss_src2 = content.scan(src2_regex)
- total_images = ss + ss_src + ss_src2
- if total_images.length > 0
- total_images.each do |s|
- begin
- image_title = /\"(.*?)\"/
- r_content = s[0]
- remove_title = r_content.to_s.scan(image_title)
- # if remove_title.length > 0
- # r_content = r_content.gsub(/#{remove_title[0]}/, "").strip
- # end
- path_last = r_content
- path_current = ""
- # 相对路径处理
- if r_content.start_with?("../")
- relative_path_length = r_content.split("../").size - 1
- path_pre = path.split("/").size - 1 - relative_path_length
- path_pre = 0 if path_pre < 0
- path_current = path_pre == 0 ? "" : path.split("/")[0..path_pre].join("/")
- path_last = r_content.split("../").last
- elsif r_content.start_with?("/") # 根路径处理
- path_last = r_content[1..r_content.size]
- else
- path_current = path
- end
- # if r_content.include?("?")
- # new_r_content = r_content + "&raw=true"
- # else
- # new_r_content = r_content + "?raw=true"
- # end
- new_r_content = r_content
-
- unless r_content.include?("http://") || r_content.include?("https://") || r_content.include?("mailto:")
- # new_r_content = "#{path}" + new_r_content
- new_r_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{path_current}/#{path_last}&ref=#{ref}"].join
- end
- content = content.gsub(/src=\"#{r_content}\"/, "src=\"#{new_r_content}\"").gsub(/src='#{r_content}'/, "src=\"#{new_r_content}\"")
- rescue
- next
- end
- end
- end
-
- return content
- rescue
- return str
- end
- end
-
- # unix_time values for example: 1604382982
- def render_format_time_with_unix(unix_time)
- Time.at(unix_time).strftime("%Y-%m-%d %H:%M")
- end
-
- # date for example: 2020-11-01T19:57:27+08:00
- def render_format_time_with_date(date)
- date.to_time.strftime("%Y-%m-%d %H:%M")
- end
-
- def decode64_content(entry, owner, repo, ref, path=nil)
- if is_readme?(entry['type'], entry['name'])
- # content = Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
- content = entry['content']
- path = URI.escape(entry['path']).to_s.downcase.gsub("/readme.md","")
- readme_render_decode64_content(content, owner, repo, ref, path)
- else
- file_type = File.extname(entry['name'].to_s)[1..-1]
- if image_type?(file_type)
- return entry['content'].nil? ? Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content'] : entry['content']
- end
- if download_type(file_type)
- return entry['content']
- end
- render_decode64_content(entry['content'])
- end
- end
-
- def base64_to_image(path, content)
- # generate to https://git.trusite.net/pawm36ozq/-/raw/branch/master/entrn.png"
- content = Base64.decode64(content)
- File.open(path, 'wb') { |f| f.write(content) }
- end
-
- def render_download_image_url(dir_path, file_path, content)
- full_path = file_path.starts_with?("/") ? [dir_path, file_path].join("") : [dir_path, file_path].join("/")
- file_name = full_path.split("/")[-1]
- # 用户名/项目标识/文件路径
- dir_path = generate_dir_path(full_path.split("/"+file_name)[0])
-
- file_path = [dir_path, file_name].join('/')
-
- puts "##### render_download_image_url file_path: #{file_path}"
- base64_to_image(file_path, content)
- file_path = file_path[6..-1]
- File.join(base_url, file_path)
- end
-
- def generate_dir_path(dir_path)
- # tmp_dir_path
- # eg: jasder/forgeplus/raw/branch/ref
- dir_path = ["public", tmp_dir, dir_path].join('/')
- puts "#### dir_path: #{dir_path}"
- unless Dir.exists?(dir_path)
- FileUtils.mkdir_p(dir_path) ##不成功这里会抛异常
- end
- dir_path
- end
-
- def tmp_dir
- "repo"
- end
-
-end
+module RepositoriesHelper
+ def render_permission(user, project)
+ return "Admin" if user&.admin?
+ project.get_premission(user)
+ end
+
+ def render_decode64_content(str)
+ return nil if str.blank?
+ Base64.decode64(str).force_encoding("UTF-8").encode("UTF-8", invalid: :replace)
+ end
+
+ def download_type(str)
+ default_type = %w(xlsx xls ppt pptx pdf zip 7z rar exe pdb obj idb RData rdata doc docx mpp vsdx dot otf eot ttf woff woff2 mp4 mov wmv flv mpeg avi avchd webm mkv)
+ default_type.include?(str&.downcase) || str.blank?
+ end
+
+ def image_type?(str)
+ default_type = %w(png jpg gif tif psd svg bmp webp jpeg ico psd)
+ default_type.include?(str&.downcase)
+ end
+
+ def is_readme?(type, str)
+ return false if type != 'file' || str.blank?
+ readme_types = ["readme.md", "readme", "readme_en.md", "readme_zh.md", "readme_en", "readme_zh"]
+ readme_types.include?(str.to_s.downcase) || str =~ CustomRegexp::MD_REGEX
+ end
+
+ def render_commit_author(author_json)
+ return nil if author_json.blank? || (author_json["id"].blank? && author_json['name'].blank?)
+ if author_json["id"].present?
+ return find_user_by_gitea_uid author_json['id']
+ end
+ if author_json["id"].nil? && (author_json["name"].present? && author_json["email"].present?)
+ return find_user_by_login_and_mail(author_json['name'], author_json["email"])
+ end
+ end
+
+ def render_cache_commit_author(author_json)
+ if author_json["name"].present? && author_json["email"].present?
+ return find_user_in_redis_cache(author_json['name'], author_json['email'])
+ end
+ if author_json["Name"].present? && author_json["Email"].present?
+ return find_user_in_redis_cache(author_json['Name'], author_json['Email'])
+ end
+ end
+
+ def readme_render_decode64_content(str, owner, repo, ref, path)
+ return nil if str.blank?
+ begin
+ content = Base64.decode64(str).force_encoding('UTF-8')
+
+ c_regex = /\!\[.*?\]\((.*?)\)/
+ src_regex = /src=\"(.*?)\"/
+ src2_regex = /src='(.*?)'/
+ ss = content.to_s.scan(c_regex)
+ ss_src = content.scan(src_regex)
+ ss_src2 = content.scan(src2_regex)
+ total_images = ss + ss_src + ss_src2
+ if total_images.length > 0
+ total_images.each do |s|
+ begin
+ image_title = /\"(.*?)\"/
+ r_content = s[0]
+ remove_title = r_content.to_s.scan(image_title)
+ # if remove_title.length > 0
+ # r_content = r_content.gsub(/#{remove_title[0]}/, "").strip
+ # end
+ path_last = r_content
+ path_current = ""
+ # 相对路径处理
+ if r_content.start_with?("../")
+ relative_path_length = r_content.split("../").size - 1
+ path_pre = path.split("/").size - 1 - relative_path_length
+ path_pre = 0 if path_pre < 0
+ path_current = path_pre == 0 ? "" : path.split("/")[0..path_pre].join("/")
+ path_last = r_content.split("../").last
+ elsif r_content.start_with?("/") # 根路径处理
+ path_last = r_content[1..r_content.size]
+ else
+ path_current = path
+ end
+ # if r_content.include?("?")
+ # new_r_content = r_content + "&raw=true"
+ # else
+ # new_r_content = r_content + "?raw=true"
+ # end
+ new_r_content = r_content
+
+ unless r_content.include?("http://") || r_content.include?("https://") || r_content.include?("mailto:")
+ # new_r_content = "#{path}" + new_r_content
+ new_r_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{path_current}/#{path_last}&ref=#{ref}"].join
+ end
+ content = content.gsub(/src=\"#{r_content}\"/, "src=\"#{new_r_content}\"").gsub(/src='#{r_content}'/, "src=\"#{new_r_content}\"")
+ rescue
+ next
+ end
+ end
+ end
+
+ return content
+ rescue
+ return str
+ end
+ end
+
+ # author hui.he
+ def new_readme_render_decode64_content(str, owner, repo, ref, readme_path, readme_name)
+ file_path = readme_path.include?('/') ? readme_path.gsub("/#{readme_name}", '') : readme_path.gsub("#{readme_name}", '')
+ return nil if str.blank?
+ content = Base64.decode64(str).force_encoding('UTF-8')
+ s_regex = /\[.*?\]\((.*?)\)/
+ src_regex = /src=\"(.*?)\"/
+ ss = content.to_s.scan(s_regex)
+ ss_src = content.to_s.scan(src_regex)
+ total_sources = ss + ss_src
+ total_sources.uniq!
+ total_sources.each do |s|
+ begin
+ s_content = s[0]
+ # 链接直接跳过不做替换
+ next if s_content.starts_with?('http://') || s_content.starts_with?('https://') || s_content.starts_with?('mailto:') || s_content.blank?
+ ext = File.extname(s_content)[1..-1]
+
+ if image_type?(ext) || download_type(ext)
+ s_content = File.expand_path(s_content, file_path)
+ s_content = s_content.split("#{Rails.root}/")[1]
+ # content = content.gsub(s[0], "/#{s_content}")
+ s_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{s_content}&ref=#{ref}"].join
+ content = content.gsub(s[0], s_content)
+ 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]
+ content = content.gsub(s[0], "/#{s_content}")
+ end
+ rescue
+ next
+ end
+ end
+
+ return content
+ rescue
+ return str
+ end
+
+ # unix_time values for example: 1604382982
+ def render_format_time_with_unix(unix_time)
+ Time.at(unix_time).strftime("%Y-%m-%d %H:%M")
+ end
+
+ # date for example: 2020-11-01T19:57:27+08:00
+ def render_format_time_with_date(date)
+ date.to_time.strftime("%Y-%m-%d %H:%M")
+ end
+
+ def readme_decode64_content(entry, owner, repo, ref, path=nil)
+ Rails.logger.info("entry===#{entry["type"]} #{entry["name"]}")
+ content = Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
+ Rails.logger.info("content===#{content}")
+ # readme_render_decode64_content(content, owner, repo, ref)
+ new_readme_render_decode64_content(content, owner, repo, ref, entry['path'], entry['name'])
+ end
+
+ def decode64_content(entry, owner, repo, ref, path=nil)
+ if is_readme?(entry['type'], entry['name'])
+ Rails.logger.info("entry===#{entry["type"]} #{entry["name"]}")
+ content = Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
+ Rails.logger.info("content===#{content}")
+ # readme_render_decode64_content(content, owner, repo, ref)
+ return Base64.decode64(content).force_encoding('UTF-8')
+ else
+ file_type = File.extname(entry['name'].to_s)[1..-1]
+ if image_type?(file_type)
+ return entry['content'].nil? ? Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content'] : entry['content']
+ end
+ if download_type(file_type)
+ return entry['content']
+ end
+ render_decode64_content(entry['content'])
+ end
+ end
+
+ def base64_to_image(path, content)
+ # generate to https://git.trusite.net/pawm36ozq/-/raw/branch/master/entrn.png"
+ content = Base64.decode64(content)
+ File.open(path, 'wb') { |f| f.write(content) }
+ end
+
+ def render_download_image_url(dir_path, file_path, content)
+ full_path = file_path.starts_with?("/") ? [dir_path, file_path].join("") : [dir_path, file_path].join("/")
+ file_name = full_path.split("/")[-1]
+ # 用户名/项目标识/文件路径
+ dir_path = generate_dir_path(full_path.split("/"+file_name)[0])
+
+ file_path = [dir_path, file_name].join('/')
+
+ puts "##### render_download_image_url file_path: #{file_path}"
+ base64_to_image(file_path, content)
+ file_path = file_path[6..-1]
+ File.join(base_url, file_path)
+ end
+
+ def generate_dir_path(dir_path)
+ # tmp_dir_path
+ # eg: jasder/forgeplus/raw/branch/ref
+ dir_path = ["public", tmp_dir, dir_path].join('/')
+ puts "#### dir_path: #{dir_path}"
+ unless Dir.exists?(dir_path)
+ FileUtils.mkdir_p(dir_path) ##不成功这里会抛异常
+ end
+ dir_path
+ end
+
+ def tmp_dir
+ "repo"
+ end
+
+end
diff --git a/app/imports/admins/new_import_user_from_excel.rb b/app/imports/admins/new_import_user_from_excel.rb
new file mode 100644
index 000000000..b9a452cf1
--- /dev/null
+++ b/app/imports/admins/new_import_user_from_excel.rb
@@ -0,0 +1,15 @@
+class Admins::NewImportUserFromExcel < BaseImportXlsx
+ UserData = Struct.new(:login, :email, :password, :nickname)
+
+ def read_each(&block)
+ sheet.each_row_streaming(pad_cells: true, offset: 1) do |row|
+ data = row.map(&method(:cell_value))[0..3]
+ block.call UserData.new(*data)
+ end
+ end
+
+ private
+ def cell_value(obj)
+ obj&.cell_value
+ end
+end
\ No newline at end of file
diff --git a/app/interactors/gitea/register_interactor.rb b/app/interactors/gitea/register_interactor.rb
index d8dbc6cad..e188809ac 100644
--- a/app/interactors/gitea/register_interactor.rb
+++ b/app/interactors/gitea/register_interactor.rb
@@ -13,7 +13,7 @@ module Gitea
end
def success?
- @error.nil?
+ @error.nil? && @result[:status].to_s == "success"
end
def result
diff --git a/app/jobs/send_template_message_job.rb b/app/jobs/send_template_message_job.rb
index 9cc819a11..557d4d0fa 100644
--- a/app/jobs/send_template_message_job.rb
+++ b/app/jobs/send_template_message_job.rb
@@ -4,6 +4,24 @@ class SendTemplateMessageJob < ApplicationJob
def perform(source, *args)
Rails.logger.info "SendTemplateMessageJob [args] #{args}"
case source
+ when 'CustomTip'
+ receivers_id, template_id, props = args[0], args[1], args[2]
+ template = MessageTemplate.find_by_id(template_id)
+ return unless template.present?
+ receivers = User.where(id: receivers_id).or(User.where(mail: receivers_id))
+ not_exists_receivers = receivers_id - receivers.pluck(:id) - receivers.pluck(:mail)
+ receivers_string, content, notification_url = MessageTemplate::CustomTip.get_message_content(receivers, template, props)
+ Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {receivers_id: receivers_id, template_id: template_id, props: props})
+ receivers.find_each do |receiver|
+ receivers_email_string, email_title, email_content = MessageTemplate::CustomTip.get_email_message_content(receiver, template, props)
+ Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content)
+ end
+ not_exists_receivers.each do |mail|
+ valid_email_regex = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i
+ next unless (mail =~ valid_email_regex)
+ email_title, email_content = MessageTemplate::CustomTip.get_email_content(template, props)
+ Notice::Write::EmailCreateService.call(mail, email_title, email_content)
+ end
when 'FollowTip'
watcher_id = args[0]
watcher = Watcher.find_by_id(watcher_id)
diff --git a/app/libs/custom_regexp.rb b/app/libs/custom_regexp.rb
index 25c3ae988..e778c5499 100644
--- a/app/libs/custom_regexp.rb
+++ b/app/libs/custom_regexp.rb
@@ -1,7 +1,7 @@
module CustomRegexp
PHONE = /1\d{10}/
EMAIL = /\A[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/
- LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
+ LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]{4,15}\z/ #只含有数字、字母、下划线不能以下划线开头和结尾
LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/
NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/
PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/
@@ -11,5 +11,5 @@ module CustomRegexp
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
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
-
+ MD_REGEX = /^.+(\.[m|M][d|D])$/
end
diff --git a/app/models/message_template.rb b/app/models/message_template.rb
index f34200132..50de7d8ef 100644
--- a/app/models/message_template.rb
+++ b/app/models/message_template.rb
@@ -16,8 +16,7 @@ class MessageTemplate < ApplicationRecord
PLATFORM = 'GitLink'
def self.build_init_data
- MessageTemplate::IssueAssignerExpire.destroy_all
- MessageTemplate::IssueCreatorExpire.destroy_all
+ MessageTemplate.where.not(type: 'MessageTemplate::CustomTip').destroy_all
self.create(type: 'MessageTemplate::FollowedTip', sys_notice: '{nickname} 关注了你', notification_url: '{baseurl}/{login}')
email_html = File.read("#{email_template_html_dir}/issue_assigned.html")
self.create(type: 'MessageTemplate::IssueAssigned', sys_notice: '{nickname1}在 {nickname2}/{repository} 指派给你一个疑修:{title}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 指派给你一个疑修")
diff --git a/app/models/message_template/custom_tip.rb b/app/models/message_template/custom_tip.rb
new file mode 100644
index 000000000..fdb68155c
--- /dev/null
+++ b/app/models/message_template/custom_tip.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::CustomTip < MessageTemplate
+
+ # MessageTemplate::CustomTip.get_message_content(User.where(login: 'yystopf'), "hahah")
+ def self.get_message_content(receivers, template, props={})
+ return '', '', '' if receivers.blank? || template.blank?
+ content = template.sys_notice
+ notification_url = template.notification_url
+ props.each do |k, v|
+ content.gsub!("{#{k}}", v)
+ notification_url.gsub!("{#{k}}", v)
+ end
+ notification_url.gsub!('{baseurl}', base_url)
+ return receivers_string(receivers), content, notification_url
+ rescue => e
+ Rails.logger.info("MessageTemplate::CustomTip.get_message_content [ERROR] #{e}")
+ return '', '', ''
+ end
+
+ def self.get_email_message_content(receiver, template, props={})
+ return '', '', '' if receiver.blank? || template.blank?
+ title = template.email_title
+ content = template.email
+ props.each do |k, v|
+ title.gsub!("{#{k}}", v)
+ content.gsub!("{#{k}}", v)
+ end
+ content.gsub!('{receiver}', receiver&.real_name)
+ title.gsub!('{platform}', PLATFORM)
+ content.gsub!('{platform}', PLATFORM)
+ content.gsub!('{baseurl}', base_url)
+
+ return receiver&.mail, title, content
+ rescue => e
+ Rails.logger.info("MessageTemplate::CustomTip.get_email_message_content [ERROR] #{e}")
+ return '', '', ''
+ end
+
+ def self.get_email_content(template, props = {})
+ return '', '', '' if template.blank?
+ title = template.email_title
+ content = template.email
+ props.each do |k, v|
+ title.gsub!("{#{k}}", v)
+ content.gsub!("{#{k}}", v)
+ end
+ title.gsub!('{platform}', PLATFORM)
+ content.gsub!('{platform}', PLATFORM)
+
+ return title, content
+ rescue => e
+ Rails.logger.info("MessageTemplate::CustomTip.get_email_content [ERROR] #{e}")
+ return '', ''
+ end
+end
\ No newline at end of file
diff --git a/app/models/message_template/project_setting_changed.rb b/app/models/message_template/project_setting_changed.rb
index 0920dfe7a..ab597122a 100644
--- a/app/models/message_template/project_setting_changed.rb
+++ b/app/models/message_template/project_setting_changed.rb
@@ -141,6 +141,7 @@ class MessageTemplate::ProjectSettingChanged < MessageTemplate
navbar.gsub!('devops', '工作流')
navbar.gsub!('versions', '里程碑')
navbar.gsub!('resources', '资源库')
+ navbar.gsub!('services', '服务')
if change_count > 1
content.sub!('{ifnavbar}', '
')
else
@@ -290,6 +291,7 @@ class MessageTemplate::ProjectSettingChanged < MessageTemplate
navbar.gsub!('devops', '工作流')
navbar.gsub!('versions', '里程碑')
navbar.gsub!('resources', '资源库')
+ navbar.gsub!('services', '服务')
if change_count > 1
content.sub!('{ifnavbar}', '
')
else
diff --git a/app/models/project.rb b/app/models/project.rb
index a35fbf387..39fd55906 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1,411 +1,420 @@
-# == Schema Information
-#
-# Table name: projects
-#
-# id :integer not null, primary key
-# name :string(255) default(""), not null
-# description :text(4294967295)
-# homepage :string(255) default("")
-# is_public :boolean default("1"), not null
-# parent_id :integer
-# created_on :datetime
-# updated_on :datetime
-# identifier :string(255)
-# status :integer default("1"), not null
-# lft :integer
-# rgt :integer
-# inherit_members :boolean default("0"), not null
-# project_type :integer default("0")
-# hidden_repo :boolean default("0"), not null
-# attachmenttype :integer default("1")
-# user_id :integer
-# dts_test :integer default("0")
-# enterprise_name :string(255)
-# organization_id :integer
-# project_new_type :integer
-# gpid :integer
-# forked_from_project_id :integer
-# forked_count :integer default("0")
-# publish_resource :integer default("0")
-# visits :integer default("0")
-# hot :integer default("0")
-# invite_code :string(255)
-# qrcode :string(255)
-# qrcode_expiretime :integer default("0")
-# script :text(65535)
-# training_status :integer default("0")
-# rep_identifier :string(255)
-# project_category_id :integer
-# project_language_id :integer
-# praises_count :integer default("0")
-# watchers_count :integer default("0")
-# issues_count :integer default("0")
-# pull_requests_count :integer default("0")
-# language :string(255)
-# versions_count :integer default("0")
-# issue_tags_count :integer default("0")
-# closed_issues_count :integer default("0")
-# open_devops :boolean default("0")
-# gitea_webhook_id :integer
-# open_devops_count :integer default("0")
-# recommend :boolean default("0")
-# platform :integer default("0")
-# license_id :integer
-# ignore_id :integer
-# default_branch :string(255) default("master")
-# website :string(255)
-# lesson_url :string(255)
-# is_pinned :boolean default("0")
-# recommend_index :integer default("0")
-#
-# Indexes
-#
-# index_projects_on_forked_from_project_id (forked_from_project_id)
-# index_projects_on_identifier (identifier)
-# index_projects_on_invite_code (invite_code)
-# index_projects_on_is_public (is_public)
-# index_projects_on_lft (lft)
-# index_projects_on_license_id (license_id)
-# index_projects_on_name (name)
-# index_projects_on_platform (platform)
-# index_projects_on_project_category_id (project_category_id)
-# index_projects_on_project_language_id (project_language_id)
-# index_projects_on_project_type (project_type)
-# index_projects_on_recommend (recommend)
-# index_projects_on_rgt (rgt)
-# index_projects_on_status (status)
-# index_projects_on_updated_on (updated_on)
-#
-
-class Project < ApplicationRecord
- include Matchable
- include Publicable
- include Watchable
- include ProjectOperable
- include Dcodes
-
- # common:开源托管项目
- # mirror:普通镜像项目,没有定时同步功能
- # sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作
- #
- enum project_type: { sync_mirror: 2, mirror: 1, common: 0 }
-
- # forge: trustie平台项目, educoder: educoder平台项目, 默认为forge平台
- enum platform: { forge: 0, educoder: 1 }
-
- belongs_to :ignore, optional: true
- belongs_to :license, optional: true
- belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, optional: true
- belongs_to :organization_extension, foreign_key: :user_id, primary_key: :organization_id, optional: true, counter_cache: :num_projects
- belongs_to :project_category, optional: true , :counter_cache => true
- belongs_to :project_language, optional: true , :counter_cache => true
- belongs_to :forked_from_project, class_name: 'Project', optional: true, foreign_key: :forked_from_project_id
- has_many :project_trends, dependent: :destroy
- has_many :watchers, as: :watchable, dependent: :destroy
- has_many :fork_users, dependent: :destroy
- has_many :forked_users, class_name: 'ForkUser', foreign_key: :fork_project_id, dependent: :destroy
- has_many :forked_projects, class_name: 'Project', foreign_key: :forked_from_project_id
- has_one :project_educoder, dependent: :destroy
-
- has_one :project_score, dependent: :destroy
- has_one :repository, dependent: :destroy
- has_many :pull_requests, dependent: :destroy
- has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy
- has_many :issues, dependent: :destroy
- # has_many :user_grades, dependent: :destroy
- has_many :attachments, as: :container, dependent: :destroy
- has_one :project_score, dependent: :destroy
- has_many :versions, -> { order("versions.created_on DESC, versions.name DESC") }, dependent: :destroy
- has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
- has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
- has_one :project_detail, dependent: :destroy
- has_many :project_units, dependent: :destroy
- has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy
- has_many :pinned_projects, dependent: :destroy
- has_many :has_pinned_users, through: :pinned_projects, source: :user
- has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
- after_create :incre_user_statistic, :incre_platform_statistic
- after_save :check_project_members
- before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data
- before_destroy :decre_project_common, :decre_forked_from_project_count
- after_destroy :decre_user_statistic, :decre_platform_statistic
- scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)}
- scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)}
- scope :recommend, -> { visible.project_statics_select.where(recommend: true) }
- scope :pinned, -> {where(is_pinned: true)}
-
- delegate :content, to: :project_detail, allow_nil: true
- delegate :name, to: :license, prefix: true, allow_nil: true
-
- def self.all_visible(user_id=nil)
- user_projects_sql = Project.joins(:owner).where(users: {type: 'User'}).to_sql
- org_public_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'common'})).to_sql
- if user_id.present?
- org_limit_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'limited'})).to_sql
- org_privacy_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension, :organization_users).where(organization_extensions: {visibility: 'privacy'}, organization_users: {user_id: user_id})).to_sql
- return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } UNION #{ org_limit_projects_sql } UNION #{org_privacy_projects_sql} ) AS projects").visible
- else
- return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } ) AS projects").visible
- end
- end
-
- def reset_cache_data
- CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id)
- if changes[:user_id].present?
- CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first)
- CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last)
- end
- if changes[:project_language_id].present?
- first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first)
- last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last)
- CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id)
- CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id)
- CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1})
- CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1})
- end
- if changes[:is_public].present?
- if changes[:is_public][0] && !changes[:is_public][1]
- CacheAsyncClearJob.perform_later('project_rank_service', self.id)
- end
- if !changes[:is_public][0] && changes[:is_public][1]
- $redis_cache.srem("v2-project-rank-deleted", self.id)
- end
- end
- end
-
- def decre_project_common
- CacheAsyncClearJob.perform_later('project_common_service', self.id)
- end
-
- def decre_forked_from_project_count
- forked_project = self.forked_from_project
- if forked_project.present?
- forked_project.decrement(:forked_count, 1)
- forked_project.save
- end
- end
-
- def incre_user_statistic
- CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}, self.user_id)
- end
-
- def decre_user_statistic
- CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}, self.user_id)
- end
-
- def incre_platform_statistic
- CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1})
- end
-
- def decre_platform_statistic
- CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1})
- end
-
- def is_full_public
- owner = self.owner
- if owner.is_a?(Organization)
- return self.is_public && owner&.visibility == "common"
- else
- return self.is_public
- end
- end
-
- def reset_unmember_followed
- if changes[:is_public].present? && changes[:is_public] == [true, false]
- self.watchers.where.not(user_id: self.all_collaborators).destroy_all
- end
- end
-
- def set_invite_code
- if self.invite_code.nil?
- self.invite_code= self.generate_dcode('invite_code', 6)
- end
- end
-
- def set_recommend_and_is_pinned
- self.recommend = self.recommend_index.zero? ? false : true
- # 私有项目不允许设置精选和推荐
- unless self.is_public
- self.recommend = false
- self.recommend_index = 0
- self.is_pinned = false
- end
- end
-
- def self.search_project(search)
- ransack(name_or_identifier_cont: search)
- end
- # 创建者
- def creator
- User.find(user_id).full_name
- end
-
- def members_user_infos
- members.joins(:roles).where("roles.name in ('Manager', 'Developer', 'Reporter')").joins("left join users on members.user_id = users.id ").includes(:user).where("users.type = ?", "User")
- # members.joins("left join users on members.user_id = users.id").select("users.id", "users.login","users.firstname","users.lastname")
- # .pluck("users.id", "users.login","users.lastname", "users.firstname")
- end
-
- def to_param
- self.identifier.parameterize
- end
-
- def get_issues_count(status_id)
- if status_id.present?
- self&.issues.issue_issue.select(:id, :status_id).where(status_id: status_id)&.pluck(:id).size
- else
- self&.issues.issue_issue.select(:id)&.pluck(:id).size
- end
- end
-
- def get_pull_requests_count(status_id)
- if status_id.present?
- self&.pull_requests.select(:id, :status).where(status: status_id)&.pluck(:id).size
- else
- self&.pull_requests.select(:id)&.pluck(:id).size
- end
- end
-
- #创建项目管理员
- def check_project_members
- return if owner.is_a?(Organization)
- unless members.present? && members.exists?(user_id: self.user_id)
- member_params = {
- user_id: self.user_id,
- project_id: self.id
- }
- user_member = Member.new(member_params)
- if user_member.save
- role_id = Role.select(:id,:position).where(position: 3)&.first&.id
- MemberRole.create!(member_id: user_member.id ,role_id: role_id)
- end
- end
- end
-
-
- def self.init_bluck_repository
- Project.includes(:repository).find_each do |project|
- puts project.id
- next if project.owner.blank?
- if project.repository.blank?
- puts "########### start create repositoy #############"
- Repository.create!(project_id: project.id, identifier: Project.generate_identifier, user_id: project&.owner&.id)
- end
- end
- end
-
- def self.generate_identifier
- str_arr = (("a".."z").to_a + ("A".."Z").to_a)
-
- str = str_arr.shuffle[0..8].join
- while Repository.exists?(identifier: str)
- str = str_arr.shuffle[0..8].join
- end
- str
- end
-
- def self.list_user_projects(user_id)
- projects = Project.is_private.select(:id,:user_id)
- user_not_show_1 = projects.where("user_id != ?",user_id).pluck(:id).uniq
-
- user_show_2 = projects.joins(:members).where("members.user_id = ?", user_id).pluck(:id).uniq
- Project.where.not(id: (user_not_show_1 - user_show_2).uniq)
- end
-
- def members_count
- members.select(:id).size
- end
-
-
- def can_visited?
- is_public? || User.current.admin? || member?(User.current)
- end
-
- def releases_size(current_user_id, type)
- if current_user_id == self.user_id && type.to_s == "all"
- self.repository.version_releases_count
- else
- self.repository.version_releases.releases_size
- end
- end
-
- def contributor_users
- self.pull_requests.select(:user_id).pluck(:user_id).uniq.size
- end
-
- def open_issues_count
- issues_count - closed_issues_count
- end
-
- def numerical_for_project_type
- self.class.name.constantize.project_types["#{self.project_type}"]
- end
-
- def watched_by? user
- watchers.pluck(:user_id).include? user&.id
- end
-
- def praised_by? user
- praise_treads.pluck(:user_id).include? user&.id
- end
-
- def get_premission user
- return "Owner" if owner?(user)
- return "Manager" if manager?(user)
- return "Developer" if develper?(user)
- return "Reporter" if reporter?(user)
-
- return ""
- end
-
- def fork_project
- Project.find_by(id: self.forked_from_project_id)
- end
-
- def self.members_projects(member_user_id)
- joins(:members).where(members: { user_id: member_user_id})
- end
-
- def self.find_with_namespace(namespace_path, identifier)
- logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} "
-
- user = Owner.find_by_login namespace_path
- project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
- return nil if project.blank?
-
- [project, user]
- end
-
- def ci_reactivate?
- open_devops_count > 0
- end
-
- def ci_reactivate!(ci_repo)
- ci_repo.update_column(:repo_active, 1)
- update_column(:open_devops, true)
- increment!(:open_devops_count)
- end
-
- def self.sync_educoder_shixun(url, private_token, page, per_page)
- SyncEducoderShixunJob.perform_later(url, private_token, page, per_page)
- end
-
- def self.update_common_projects_count!
- ps = ProjectStatistic.first
- ps.increment!(:common_projects_count) unless ps.blank?
- end
-
- def self.update_mirror_projects_count!
- ps = ProjectStatistic.first
- ps.increment!(:mirror_projects_count) unless ps.blank?
- end
-
- def set_updated_on(time)
- return if time.blank?
- update_column(:updated_on, time)
- end
-
- def is_transfering
- applied_transfer_project&.common? ? true : false
- end
-end
+# == Schema Information
+#
+# Table name: projects
+#
+# id :integer not null, primary key
+# name :string(255) default(""), not null
+# description :text(4294967295)
+# homepage :string(255) default("")
+# is_public :boolean default("1"), not null
+# parent_id :integer
+# created_on :datetime
+# updated_on :datetime
+# identifier :string(255)
+# status :integer default("1"), not null
+# lft :integer
+# rgt :integer
+# inherit_members :boolean default("0"), not null
+# project_type :integer default("0")
+# hidden_repo :boolean default("0"), not null
+# attachmenttype :integer default("1")
+# user_id :integer
+# dts_test :integer default("0")
+# enterprise_name :string(255)
+# organization_id :integer
+# project_new_type :integer
+# gpid :integer
+# forked_from_project_id :integer
+# forked_count :integer default("0")
+# publish_resource :integer default("0")
+# visits :integer default("0")
+# hot :integer default("0")
+# invite_code :string(255)
+# qrcode :string(255)
+# qrcode_expiretime :integer default("0")
+# script :text(65535)
+# training_status :integer default("0")
+# rep_identifier :string(255)
+# project_category_id :integer
+# project_language_id :integer
+# license_id :integer
+# ignore_id :integer
+# praises_count :integer default("0")
+# watchers_count :integer default("0")
+# issues_count :integer default("0")
+# pull_requests_count :integer default("0")
+# language :string(255)
+# versions_count :integer default("0")
+# issue_tags_count :integer default("0")
+# closed_issues_count :integer default("0")
+# open_devops :boolean default("0")
+# gitea_webhook_id :integer
+# open_devops_count :integer default("0")
+# recommend :boolean default("0")
+# platform :integer default("0")
+# default_branch :string(255) default("master")
+# website :string(255)
+# lesson_url :string(255)
+# is_pinned :boolean default("0")
+# recommend_index :integer default("0")
+#
+# Indexes
+#
+# index_projects_on_forked_from_project_id (forked_from_project_id)
+# index_projects_on_identifier (identifier)
+# index_projects_on_invite_code (invite_code)
+# index_projects_on_is_public (is_public)
+# index_projects_on_lft (lft)
+# index_projects_on_license_id (license_id)
+# index_projects_on_name (name)
+# index_projects_on_platform (platform)
+# index_projects_on_project_category_id (project_category_id)
+# index_projects_on_project_language_id (project_language_id)
+# index_projects_on_project_type (project_type)
+# index_projects_on_recommend (recommend)
+# index_projects_on_rgt (rgt)
+# index_projects_on_status (status)
+# index_projects_on_updated_on (updated_on)
+#
+
+class Project < ApplicationRecord
+ include Matchable
+ include Publicable
+ include Watchable
+ include ProjectOperable
+ include Dcodes
+
+ # common:开源托管项目
+ # mirror:普通镜像项目,没有定时同步功能
+ # sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作
+ #
+ enum project_type: { sync_mirror: 2, mirror: 1, common: 0 }
+
+ # forge: trustie平台项目, educoder: educoder平台项目, 默认为forge平台
+ enum platform: { forge: 0, educoder: 1 }
+
+ belongs_to :ignore, optional: true
+ belongs_to :license, optional: true
+ belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, optional: true
+ belongs_to :organization_extension, foreign_key: :user_id, primary_key: :organization_id, optional: true, counter_cache: :num_projects
+ belongs_to :project_category, optional: true , :counter_cache => true
+ belongs_to :project_language, optional: true , :counter_cache => true
+ belongs_to :forked_from_project, class_name: 'Project', optional: true, foreign_key: :forked_from_project_id
+ has_many :project_trends, dependent: :destroy
+ has_many :watchers, as: :watchable, dependent: :destroy
+ has_many :fork_users, dependent: :destroy
+ has_many :forked_users, class_name: 'ForkUser', foreign_key: :fork_project_id, dependent: :destroy
+ has_many :forked_projects, class_name: 'Project', foreign_key: :forked_from_project_id
+ has_one :project_educoder, dependent: :destroy
+
+ has_one :project_score, dependent: :destroy
+ has_one :repository, dependent: :destroy
+ has_many :pull_requests, dependent: :destroy
+ has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy
+ has_many :issues, dependent: :destroy
+ # has_many :user_grades, dependent: :destroy
+ has_many :attachments, as: :container, dependent: :destroy
+ has_one :project_score, dependent: :destroy
+ has_many :versions, -> { order("versions.created_on DESC, versions.name DESC") }, dependent: :destroy
+ has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
+ has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
+ has_one :project_detail, dependent: :destroy
+ has_many :project_units, dependent: :destroy
+ has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy
+ has_many :pinned_projects, dependent: :destroy
+ has_many :has_pinned_users, through: :pinned_projects, source: :user
+ has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
+ has_many :user_trace_tasks, 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
+ before_destroy :decre_project_common, :decre_forked_from_project_count
+ after_destroy :decre_user_statistic, :decre_platform_statistic
+ scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)}
+ scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)}
+ scope :recommend, -> { visible.project_statics_select.where(recommend: true) }
+ scope :pinned, -> {where(is_pinned: true)}
+
+ delegate :content, to: :project_detail, allow_nil: true
+ delegate :name, to: :license, prefix: true, allow_nil: true
+
+ validate :validate_sensitive_string
+
+ def self.all_visible(user_id=nil)
+ user_projects_sql = Project.joins(:owner).where(users: {type: 'User'}).to_sql
+ org_public_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'common'})).to_sql
+ if user_id.present?
+ org_limit_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'limited'})).to_sql
+ org_privacy_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension, :organization_users).where(organization_extensions: {visibility: 'privacy'}, organization_users: {user_id: user_id})).to_sql
+ return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } UNION #{ org_limit_projects_sql } UNION #{org_privacy_projects_sql} ) AS projects").visible
+ else
+ return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } ) AS projects").visible
+ end
+ end
+
+ def reset_cache_data
+ CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id)
+ if changes[:user_id].present?
+ CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first)
+ CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last)
+ end
+ if changes[:project_language_id].present?
+ first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first)
+ last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last)
+ CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id)
+ CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id)
+ CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1})
+ CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1})
+ end
+ if changes[:is_public].present?
+ if changes[:is_public][0] && !changes[:is_public][1]
+ CacheAsyncClearJob.perform_later('project_rank_service', self.id)
+ end
+ if !changes[:is_public][0] && changes[:is_public][1]
+ $redis_cache.srem("v2-project-rank-deleted", self.id)
+ end
+ end
+ end
+
+ def decre_project_common
+ CacheAsyncClearJob.perform_later('project_common_service', self.id)
+ end
+
+ def decre_forked_from_project_count
+ forked_project = self.forked_from_project
+ if forked_project.present?
+ forked_project.decrement(:forked_count, 1)
+ forked_project.save
+ end
+ end
+
+ def incre_user_statistic
+ CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}, self.user_id)
+ end
+
+ def decre_user_statistic
+ CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}, self.user_id)
+ end
+
+ def incre_platform_statistic
+ CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1})
+ end
+
+ def decre_platform_statistic
+ CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1})
+ end
+
+ def is_full_public
+ owner = self.owner
+ if owner.is_a?(Organization)
+ return self.is_public && owner&.visibility == "common"
+ else
+ return self.is_public
+ end
+ end
+
+ def reset_unmember_followed
+ if changes[:is_public].present? && changes[:is_public] == [true, false]
+ self.watchers.where.not(user_id: self.all_collaborators).destroy_all
+ end
+ end
+
+ def set_invite_code
+ if self.invite_code.nil?
+ self.invite_code= self.generate_dcode('invite_code', 6)
+ end
+ end
+
+ def set_recommend_and_is_pinned
+ self.recommend = self.recommend_index.zero? ? false : true
+ # 私有项目不允许设置精选和推荐
+ unless self.is_public
+ self.recommend = false
+ self.recommend_index = 0
+ self.is_pinned = false
+ end
+ end
+
+ def self.search_project(search)
+ ransack(name_or_identifier_cont: search)
+ end
+ # 创建者
+ def creator
+ User.find(user_id).full_name
+ end
+
+ def members_user_infos
+ members.joins(:roles).where("roles.name in ('Manager', 'Developer', 'Reporter')").joins("left join users on members.user_id = users.id ").includes(:user).where("users.type = ?", "User")
+ # members.joins("left join users on members.user_id = users.id").select("users.id", "users.login","users.firstname","users.lastname")
+ # .pluck("users.id", "users.login","users.lastname", "users.firstname")
+ end
+
+ def to_param
+ self.identifier.parameterize
+ end
+
+ def get_issues_count(status_id)
+ if status_id.present?
+ self&.issues.issue_issue.select(:id, :status_id).where(status_id: status_id)&.pluck(:id).size
+ else
+ self&.issues.issue_issue.select(:id)&.pluck(:id).size
+ end
+ end
+
+ def get_pull_requests_count(status_id)
+ if status_id.present?
+ self&.pull_requests.select(:id, :status).where(status: status_id)&.pluck(:id).size
+ else
+ self&.pull_requests.select(:id)&.pluck(:id).size
+ end
+ end
+
+ #创建项目管理员
+ def check_project_members
+ return if owner.is_a?(Organization)
+ unless members.present? && members.exists?(user_id: self.user_id)
+ member_params = {
+ user_id: self.user_id,
+ project_id: self.id
+ }
+ user_member = Member.new(member_params)
+ if user_member.save
+ role_id = Role.select(:id,:position).where(position: 3)&.first&.id
+ MemberRole.create!(member_id: user_member.id ,role_id: role_id)
+ end
+ end
+ end
+
+
+ def self.init_bluck_repository
+ Project.includes(:repository).find_each do |project|
+ puts project.id
+ next if project.owner.blank?
+ if project.repository.blank?
+ puts "########### start create repositoy #############"
+ Repository.create!(project_id: project.id, identifier: Project.generate_identifier, user_id: project&.owner&.id)
+ end
+ end
+ end
+
+ def self.generate_identifier
+ str_arr = (("a".."z").to_a + ("A".."Z").to_a)
+
+ str = str_arr.shuffle[0..8].join
+ while Repository.exists?(identifier: str)
+ str = str_arr.shuffle[0..8].join
+ end
+ str
+ end
+
+ def self.list_user_projects(user_id)
+ projects = Project.is_private.select(:id,:user_id)
+ user_not_show_1 = projects.where("user_id != ?",user_id).pluck(:id).uniq
+
+ user_show_2 = projects.joins(:members).where("members.user_id = ?", user_id).pluck(:id).uniq
+ Project.where.not(id: (user_not_show_1 - user_show_2).uniq)
+ end
+
+ def members_count
+ members.select(:id).size
+ end
+
+
+ def can_visited?
+ is_public? || User.current.admin? || member?(User.current)
+ end
+
+ def releases_size(current_user_id, type)
+ if current_user_id == self.user_id && type.to_s == "all"
+ self.repository.version_releases_count
+ else
+ self.repository.version_releases.releases_size
+ end
+ end
+
+ def contributor_users
+ self.pull_requests.select(:user_id).pluck(:user_id).uniq.size
+ end
+
+ def open_issues_count
+ issues_count - closed_issues_count
+ end
+
+ def numerical_for_project_type
+ self.class.name.constantize.project_types["#{self.project_type}"]
+ end
+
+ def watched_by? user
+ watchers.pluck(:user_id).include? user&.id
+ end
+
+ def praised_by? user
+ praise_treads.pluck(:user_id).include? user&.id
+ end
+
+ def get_premission user
+ return "Owner" if owner?(user)
+ return "Manager" if manager?(user)
+ return "Developer" if develper?(user)
+ return "Reporter" if reporter?(user)
+
+ return ""
+ end
+
+ def fork_project
+ Project.find_by(id: self.forked_from_project_id)
+ end
+
+ def self.members_projects(member_user_id)
+ joins(:members).where(members: { user_id: member_user_id})
+ end
+
+ def self.find_with_namespace(namespace_path, identifier)
+ logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} "
+
+ user = Owner.find_by_login namespace_path
+ user = Owner.new(login: namespace_path) if user.nil?
+ project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
+ return nil if project.blank?
+
+ [project, user]
+ end
+
+ def ci_reactivate?
+ open_devops_count > 0
+ end
+
+ def ci_reactivate!(ci_repo)
+ ci_repo.update_column(:repo_active, 1)
+ update_column(:open_devops, true)
+ increment!(:open_devops_count)
+ end
+
+ def self.sync_educoder_shixun(url, private_token, page, per_page)
+ SyncEducoderShixunJob.perform_later(url, private_token, page, per_page)
+ end
+
+ def self.update_common_projects_count!
+ ps = ProjectStatistic.first
+ ps.increment!(:common_projects_count) unless ps.blank?
+ end
+
+ def self.update_mirror_projects_count!
+ ps = ProjectStatistic.first
+ ps.increment!(:mirror_projects_count) unless ps.blank?
+ end
+
+ def set_updated_on(time)
+ return if time.blank?
+ update_column(:updated_on, time)
+ end
+
+ def is_transfering
+ applied_transfer_project&.common? ? true : false
+ end
+
+ def validate_sensitive_string
+ raise("项目名称包含敏感词汇,请重新输入") if name && !HarmoniousDictionary.clean?(name)
+ raise("项目描述包含敏感词汇,请重新输入") if description && !HarmoniousDictionary.clean?(description)
+ end
+end
diff --git a/app/models/project_unit.rb b/app/models/project_unit.rb
index cc35a6b28..6ee0f2a8b 100644
--- a/app/models/project_unit.rb
+++ b/app/models/project_unit.rb
@@ -16,7 +16,7 @@
class ProjectUnit < ApplicationRecord
belongs_to :project
- enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7}
+ enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7, services: 8}
validates :unit_type, uniqueness: { scope: :project_id}
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a012b449a..12cc2fa38 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -48,6 +48,10 @@ class Repository < ApplicationRecord
self.identifier.parameterize
end
+ def url
+ self['url'].blank? ? "#{Rails.application.config_for(:configuration)['platform_url']}/#{self.owner&.login}/#{self.identifier}.git" : self['url']
+ end
+
# with repository is mirror
def set_mirror!
self.build_mirror(status: Mirror.statuses[:waiting]).save
diff --git a/app/models/user.rb b/app/models/user.rb
index 66aeb6164..bf764c148 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -175,6 +175,7 @@ class User < Owner
has_many :system_notification_histories
has_many :system_notifications, through: :system_notification_histories
has_one :trace_user, dependent: :destroy
+ has_many :user_trace_tasks, dependent: :destroy
# Groups and active users
scope :active, lambda { where(status: [STATUS_ACTIVE, STATUS_EDIT_INFO]) }
diff --git a/app/models/user_trace_task.rb b/app/models/user_trace_task.rb
new file mode 100644
index 000000000..328cb7c0b
--- /dev/null
+++ b/app/models/user_trace_task.rb
@@ -0,0 +1,25 @@
+# == Schema Information
+#
+# Table name: user_trace_tasks
+#
+# id :integer not null, primary key
+# user_id :integer
+# project_id :integer
+# branch_tag :string(255)
+# task_id :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_user_trace_tasks_on_project_id (project_id)
+# index_user_trace_tasks_on_user_id (user_id)
+#
+
+class UserTraceTask < ApplicationRecord
+
+ belongs_to :user
+ belongs_to :project
+
+
+end
diff --git a/app/services/admins/import_user_from_excel_service.rb b/app/services/admins/import_user_from_excel_service.rb
new file mode 100644
index 000000000..709551c23
--- /dev/null
+++ b/app/services/admins/import_user_from_excel_service.rb
@@ -0,0 +1,71 @@
+class Admins::ImportUserFromExcelService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :file, :result
+
+ def initialize(file)
+ @file = file
+ @result = { success: 0, fail: [] }
+ end
+
+ def call
+ raise Error, '文件不存在' if file.blank?
+ excel = Admins::NewImportUserFromExcel.new(file)
+
+ excel.read_each(&method(:save_user))
+ result
+ rescue ApplicationImport::Error => ex
+ raise Error, ex.message
+ end
+
+ private
+ def save_user(data)
+ user = find_user(data)
+ if user.blank?
+ create_user(data)
+ result[:success] +=1
+ else
+ fail_data = data.as_json
+ fail_data[:data] = fail_data.values.join(",")
+ fail_data[:message] = '用户已存在'
+ result[:fail] << fail_data
+ end
+
+ rescue Exception => ex
+ fail_data = data.as_json
+ fail_data[:data] = fail_data.values.join(",")
+ fail_data[:message] = ex.message
+ result[:fail] << fail_data
+ end
+
+ def create_user(data)
+ ActiveRecord::Base.transaction do
+ username = data.login&.gsub(/\s+/, "")
+ email = data.email&.gsub(/\s+/, "")
+ password = data.password
+ nickname = data.nickname&.gsub(/\s+/, "")
+ raise Error, "无法使用以下关键词:#{username},请重新命名" if ReversedKeyword.check_exists?(data.login)
+ Register::RemoteForm.new({username: username, email: email, password: password, platform: 'forge'}).validate!
+ user = User.new(admin: false, login: username, mail: email, nickname: nickname, platform: 'forge' , type: "User")
+ user.password = password
+ user.activate
+ raise Error, user.errors.full_messages.join(",") unless user.valid?
+ interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password})
+ if interactor.success?
+ gitea_user = interactor.result
+ result = Gitea::User::GenerateTokenService.call(username, password)
+ user.gitea_token = result['sha1']
+ user.gitea_uid = gitea_user[:body]['id']
+ UserExtension.create!(user_id: user.id) if user.save!
+ else
+ raise interactor.error, 'gitea user create error'
+ end
+
+ user
+ end
+ end
+
+ def find_user(data)
+ User.find_by(login: data.login)
+ end
+end
\ No newline at end of file
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 7d1214eb8..157263962 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -24,7 +24,8 @@ class Projects::TransferService < ApplicationService
private
def update_owner
- project.members.map{|m| m.destroy! if m.user_id == owner.id || (new_owner.is_a?(Organization) && new_owner.is_member?(m.user_id)) }
+ project.members.map{|m| m.destroy! if m.user_id == owner.id || project.member(new_owner.id) || (new_owner.is_a?(Organization) && new_owner.is_member?(m.user_id)) }
+ project.set_owner_permission(new_owner)
project.update!(user_id: new_owner.id)
end
diff --git a/app/services/trace/check_result_service.rb b/app/services/trace/check_result_service.rb
index f1dd61ab0..ff47d9a39 100644
--- a/app/services/trace/check_result_service.rb
+++ b/app/services/trace/check_result_service.rb
@@ -1,11 +1,11 @@
# 代码溯源 查询检测结果
class Trace::CheckResultService < Trace::ClientService
- attr_accessor :token, :project_name, :file_name, :page_num, :page_size
+ attr_accessor :token, :project, :file_name, :page_num, :page_size
- def initialize(token, project_name=nil, file_name=nil, page_num=1, page_size=15)
+ def initialize(token, project, file_name=nil, page_num=1, page_size=15)
@token = token
- @project_name = project_name
+ @project = project
@file_name = file_name
@page_num = page_num
@page_size = page_size
@@ -19,7 +19,7 @@ class Trace::CheckResultService < Trace::ClientService
private
def request_params
{
- product_name: project_name,
+ product_name: Digest::MD5.hexdigest(project&.id.to_s)[0...20],
file_name: file_name,
pageNum: page_num,
pageSize: page_size,
diff --git a/app/services/trace/check_service.rb b/app/services/trace/check_service.rb
index d31bbcf09..d1623445b 100644
--- a/app/services/trace/check_service.rb
+++ b/app/services/trace/check_service.rb
@@ -11,26 +11,25 @@ class Trace::CheckService < Trace::ClientService
end
def call
- result = authed_post(token, url, {data: request_params})
+ result = http_authed_post(token, url, {data: request_params})
reponse = render_response(result)
end
private
def request_params
- repo = Gitea::Repository::GetService.call(project&.owner&.login, project&.identifier)
+ repo = Gitea::Repository::GetService.call(project&.owner, project&.identifier)
{
- product_name: project&.name,
- product_type: project&.category&.name,
- code_type: project&.language&.name,
+ product_name: Digest::MD5.hexdigest(project&.id.to_s)[0...20],
+ product_type: project&.project_category&.name || '其他',
+ code_type: project&.project_language&.name || '其他',
product_desc: project&.description,
git_url: repo['clone_url'],
if_branch: if_branch,
branch_tag: branch_tag
- }
+ }.compact
end
def url
"/user/check".freeze
end
-end
-
+end
\ No newline at end of file
diff --git a/app/services/trace/client_service.rb b/app/services/trace/client_service.rb
index 72ffa8ca2..0f1449225 100644
--- a/app/services/trace/client_service.rb
+++ b/app/services/trace/client_service.rb
@@ -12,6 +12,19 @@ class Trace::ClientService < ApplicationService
conn.post(full_url(url), params[:data])
end
+ def http_authed_post(token, url, params={})
+ puts "[trace][POST] request params: #{params}"
+ puts "[trace][POST] request token: #{token}"
+ url = URI("#{full_url(url)}")
+ http = Net::HTTP.new(url.host, url.port)
+ http.read_timeout = 1200
+ request = Net::HTTP::Post.new(url)
+ request["Authorization"] = token
+ form_data = params[:data].stringify_keys.to_a
+ request.set_form form_data, 'multipart/form-data'
+ http.request(request)
+ end
+
def get(url, params={})
puts "[trace][GET] request params: #{params}"
conn.get do |req|
@@ -100,11 +113,22 @@ class Trace::ClientService < ApplicationService
end
def render_response(response)
- status = response.status
- body = JSON.parse(response&.body)
+ if response.is_a?(Faraday::Response)
+ status = response.status
+ body = JSON.parse(response&.body)
- log_error(status, body)
+ log_error(status, body)
- return [body["code"], body["data"], body["error"]]
+ return [body["code"], body["data"], body["error"]]
+ end
+
+ if response.is_a?(Net::HTTPOK)
+ status = 200
+ body = JSON.parse(response&.body)
+
+ log_error(status, body)
+
+ return [body["code"], body["data"], body["error"]]
+ end
end
end
\ No newline at end of file
diff --git a/app/services/trace/pdf_report_service.rb b/app/services/trace/pdf_report_service.rb
index e91a78b30..aa7312739 100644
--- a/app/services/trace/pdf_report_service.rb
+++ b/app/services/trace/pdf_report_service.rb
@@ -1,4 +1,7 @@
# 代码溯源 导出pdf
+require 'open-uri'
+require 'fileutils'
+
class Trace::PdfReportService < Trace::ClientService
attr_accessor :token, :task_id
@@ -9,15 +12,23 @@ class Trace::PdfReportService < Trace::ClientService
end
def call
- result = authed_get(token, url, request_params)
- response = render_response(result)
+ content = open("#{domain}#{base_url}#{url}?task_id=#{task_id}", "Authorization" => token)
+ if content.is_a?(Tempfile)
+ check_file_path
+ IO.copy_stream(content, "#{save_path}/#{task_id}.pdf")
+ return {code: 200, download_url: "/trace_task_results/#{task_id}.pdf"}
+ else
+ return {code: 404}
+ end
end
private
- def request_params
- {
- task_id: task_id
- }
+ def check_file_path
+ FileUtils.mkdir_p save_path
+ end
+
+ def save_path
+ "public/trace_task_results"
end
def url
diff --git a/app/views/admins/message_templates/_list.html.erb b/app/views/admins/message_templates/_list.html.erb
index 4082735f5..dae87d576 100644
--- a/app/views/admins/message_templates/_list.html.erb
+++ b/app/views/admins/message_templates/_list.html.erb
@@ -11,8 +11,8 @@
供项目成员(开发者、报告者)退出项目用
+ +++示例:
+
curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json
+
await octokit.request('POST /api/:owner/:repo/quit.json')
+
POST /api/:owner/:repo/quit.json
参数 | +必选 | +默认 | +类型 | +字段说明 | +
---|---|---|---|---|
owner | +是 | ++ | string | +用户登录名 | +
repo | +是 | ++ | string | +项目标识identifier | +
++返回的JSON示例:
+
{
+ "status": 0,
+ "message": "success"
+}
仓库详情
@@ -9302,6 +9368,289 @@ http://localhost:3000/api/yystopf/ceshi/webhooks/3/test.json +用户同意协议后请求的接口,创建代码溯源的账号
+ +++示例:
+
curl -X POST \
+http://localhost:3000/api/traces/trace_users.json
+
await octokit.request('POST /api/traces/trace_users.json')
+
POST api/traces/trace_users.json
++返回的JSON示例:
+
{
+ "status": 0,
+ "message": "success"
+}
+
查询项目下代码分析的结果
+ +++示例:
+
curl -X GET \
+http://localhost:3000/api/traces/yystopf/many_branch/task_results.json
+
await octokit.request('GET /api/traces/:owner/:repo/task_results.json')
+
GET api/traces/:owner/:repo/task_results.json
参数 | +必选 | +默认 | +类型 | +字段说明 | +
---|---|---|---|---|
owner | +是 | +否 | +string | +项目所有者标识 | +
repo | +是 | +否 | +string | +项目标识 | +
page | +否 | +1 | +int | +页码 | +
limit | +否 | +15 | +int | +每页数量 | +
++返回的JSON示例:
+
{
+ "data": [
+ {
+ "accuracy": "20",
+ "code_type": "C",
+ "depth": "2",
+ "detect_flag": "快速-组件级",
+ "detect_rule": "快速-组件级,2,20,,开源软件,50,10",
+ "detect_startdate": "2022-05-10 15:59:46 ",
+ "detect_status": "fail",
+ "detectflag": "快速-组件级",
+ "fail_reason": "Invalid package type",
+ "file_name": "many_branch.zip",
+ "license_process": "100",
+ "licenseparam": "开源软件",
+ "package_type": "",
+ "product_name": "84727546110",
+ "project_id": "6dbc3e42-5857-4ca4-a54d-58fd9dbf6dc5",
+ "sim_process": "100",
+ "similarity_process": "2",
+ "task_id": "15139171-091b-4316-98b1-6068970efa44",
+ "totalsize": 5,
+ "uid": "78",
+ "vuln_process": "",
+ "vulnlevel": ""
+ }
+ ]
+}
+
用户选择仓库分支进行代码分析的接口
+ +++示例:
+
curl -X POST \
+http://localhost:3000/api/traces/yystopf/many_branch/tasks.json
+
await octokit.request('POST /api/traces/:owner/:repo/tasks.json')
+
POST api/traces/:owner/:repo/tasks.json
参数 | +必选 | +默认 | +类型 | +字段说明 | +
---|---|---|---|---|
owner | +是 | +否 | +string | +项目所有者标识 | +
repo | +是 | +否 | +string | +项目标识 | +
branch_name | +是 | +否 | +string | +分支名称 | +
++返回的JSON示例:
+
{
+ "status": 0,
+ "message": "success"
+}
+
对代码分析结果进行再次分析
+ +++示例:
+
curl -X GET \
+http://localhost:3000/api/traces/yystopf/many_branch/reload_task.json
+
await octokit.request('GET /api/traces/:owner/:repo/reload_task.json')
+
GET api/traces/:owner/:repo/reload_task.json
参数 | +必选 | +默认 | +类型 | +字段说明 | +
---|---|---|---|---|
owner | +是 | +否 | +string | +项目所有者标识 | +
repo | +是 | +否 | +string | +项目标识 | +
project_id | +是 | +否 | +string | +代码分析结果里的project_id | +
++返回的JSON示例:
+
{
+ "status": 0,
+ "message": "success"
+}
+
把代码分析的结果下载到本地
+ +++示例:
+
curl -X GET \
+http://localhost:3000/api/traces/yystopf/many_branch/task_pdf.json
+
await octokit.request('GET /api/traces/:owner/:repo/task_pdf.json')
+
GET api/traces/:owner/:repo/task_pdf.json
参数 | +必选 | +默认 | +类型 | +字段说明 | +
---|---|---|---|---|
owner | +是 | +否 | +string | +项目所有者标识 | +
repo | +是 | +否 | +string | +项目标识 | +
task_id | +是 | +否 | +string | +代码分析结果里的task_id | +
++返回的JSON示例:
+
{
+ "status": 0,
+ "message": "success"
+}
+
获取合并请求详情接口
diff --git a/public/rcore-os_repo.xlsx b/public/rcore-os_repo.xlsx new file mode 100644 index 000000000..83ff6f5fa Binary files /dev/null and b/public/rcore-os_repo.xlsx differ diff --git a/public/导入用户模板.xlsx b/public/导入用户模板.xlsx new file mode 100644 index 000000000..c538bed63 Binary files /dev/null and b/public/导入用户模板.xlsx differ diff --git a/spec/models/user_trace_task_spec.rb b/spec/models/user_trace_task_spec.rb new file mode 100644 index 000000000..b2542f57a --- /dev/null +++ b/spec/models/user_trace_task_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe UserTraceTask, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end