Merge pull request '20220608版本' (#306) from Trustie/forgeplus:trustie_server into develop

This commit is contained in:
xxq250 2022-06-08 14:43:22 +08:00
commit 507d63e98c
62 changed files with 2816 additions and 1530 deletions

3
.gitignore vendored
View File

@ -84,4 +84,5 @@ redis_data/
Dockerfile Dockerfile
dump.rdb dump.rdb
.tags* .tags*
ceshi_user.xlsx ceshi_user.xlsx
public/trace_task_results

View File

@ -1,6 +1,25 @@
class AccountsController < ApplicationController class AccountsController < ApplicationController
before_action :require_login, only: [:login_check, :simple_update]
include ApplicationHelper 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 def index
render json: session render json: session
end end
@ -315,6 +334,11 @@ class AccountsController < ApplicationController
Register::CheckColumnsForm.new(check_params).validate! Register::CheckColumnsForm.new(check_params).validate!
render_ok render_ok
end end
def login_check
Register::LoginCheckColumnsForm.new(check_params.merge(user: current_user)).validate!
render_ok
end
private private
@ -383,4 +407,7 @@ class AccountsController < ApplicationController
params.permit(:username, :email, :password, :platform) params.permit(:username, :email, :password, :platform)
end end
def simple_update_params
params.permit(:username, :email, :password, :platform)
end
end end

View File

@ -2,7 +2,7 @@ class Admins::ImportUsersController < Admins::BaseController
def create def create
return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile) 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) render_ok(result)
rescue Admins::ImportUserService::Error => ex rescue Admins::ImportUserService::Error => ex
render_error(ex) render_error(ex)

View File

@ -2,8 +2,24 @@ class Admins::MessageTemplatesController < Admins::BaseController
before_action :get_template, only: [:edit, :update, :destroy] before_action :get_template, only: [:edit, :update, :destroy]
def index def index
message_templates = MessageTemplate.group(:type).count.keys message_templates = MessageTemplate.ransack(sys_notice_or_email_or_email_title_cont: params[:search]).result
@message_templates = kaminari_array_paginate(message_templates) @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 end
def edit def edit

View File

@ -14,12 +14,12 @@ class Ci::CloudAccountsController < Ci::BaseController
def create def create
flag, msg = check_bind_cloud_account! flag, msg = check_bind_cloud_account!
return render_error(msg) if flag === true return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@cloud_account = bind_account! @cloud_account = bind_account!
if @cloud_account.blank? if @cloud_account.blank?
render_error('激活失败, 请检查你的云服务器信息是否正确.') tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
else else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
@ -27,17 +27,17 @@ class Ci::CloudAccountsController < Ci::BaseController
end end
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def activate def activate
return render_error('请先认证') unless current_user.ci_certification? return tip_exception('请先认证') unless current_user.ci_certification?
begin begin
@cloud_account = Ci::CloudAccount.find params[:id] @cloud_account = Ci::CloudAccount.find params[:id]
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if @repo if @repo
return render_error('该项目已经激活') if @repo.repo_active? return tip_exception('该项目已经激活') if @repo.repo_active?
@repo.activate!(@project) @repo.activate!(@project)
else else
@repo = Ci::Repo.auto_create!(@ci_user, @project) @repo = Ci::Repo.auto_create!(@ci_user, @project)
@ -50,7 +50,7 @@ class Ci::CloudAccountsController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
end end
@ -59,39 +59,39 @@ class Ci::CloudAccountsController < Ci::BaseController
def bind def bind
flag, msg = check_bind_cloud_account! flag, msg = check_bind_cloud_account!
return render_error(msg) if flag === true return tip_exception(msg) if flag === true
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@cloud_account = bind_account! @cloud_account = bind_account!
if @cloud_account.blank? if @cloud_account.blank?
render_error('激活失败, 请检查你的云服务器信息是否正确.') tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
else else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
end end
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def trustie_bind def trustie_bind
account = params[:account].to_s 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! 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 ActiveRecord::Base.transaction do
@cloud_account = trustie_bind_account! @cloud_account = trustie_bind_account!
if @cloud_account.blank? if @cloud_account.blank?
render_error('激活失败, 请检查你的云服务器信息是否正确.') tip_exception('激活失败, 请检查你的云服务器信息是否正确.')
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
else else
current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) current_user.set_drone_step!(User::DEVOPS_UNVERIFIED)
end end
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def unbind def unbind
@ -107,18 +107,18 @@ class Ci::CloudAccountsController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def oauth_grant def oauth_grant
password = params[:password].to_s 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 oauth = current_user.oauths.last
return render_error("服务器出小差了.") if oauth.blank? return tip_exception("服务器出小差了.") if oauth.blank?
result = gitea_oauth_grant!(password, oauth) 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) current_user.set_drone_step!(User::DEVOPS_CERTIFICATION)
end end

View File

@ -30,7 +30,7 @@ class Ci::PipelinesController < Ci::BaseController
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size
if size > 0 if size > 0
render_error("#{params[:branch]}分支已经存在流水线!") tip_exception("#{params[:branch]}分支已经存在流水线!")
return return
end end
pipeline = Ci::Pipeline.new(pipeline_name: params[:pipeline_name], file_name: params[:file_name],owner: params[:owner], 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}) render_ok({id: pipeline.id})
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
# 在代码库创建文件 # 在代码库创建文件
@ -81,6 +81,7 @@ class Ci::PipelinesController < Ci::BaseController
repo_branch: pipeline.branch, repo_branch: pipeline.branch,
repo_config: pipeline.file_name repo_config: pipeline.file_name
} }
Rails.logger.info("########create_params===#{create_params.to_json}")
repo = Ci::Repo.create_repo(create_params) repo = Ci::Repo.create_repo(create_params)
repo repo
end end
@ -118,7 +119,7 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def destroy def destroy
@ -132,7 +133,7 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def content def content
@ -182,7 +183,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update_stage def update_stage
@ -192,7 +193,7 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def delete_stage def delete_stage
@ -205,7 +206,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update_stage_index(pipeline_id, show_index, diff) def update_stage_index(pipeline_id, show_index, diff)
@ -229,7 +230,7 @@ class Ci::PipelinesController < Ci::BaseController
unless steps.empty? unless steps.empty?
steps.each do |step| steps.each do |step|
unless step[:template_id] unless step[:template_id]
render_error('请选择模板!') tip_exception('请选择模板!')
return return
end end
if !step[:id] if !step[:id]
@ -246,7 +247,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def create_stage_step def create_stage_step
@ -262,7 +263,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update_stage_step def update_stage_step
@ -279,7 +280,7 @@ class Ci::PipelinesController < Ci::BaseController
render_ok render_ok
end end
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def delete_stage_step def delete_stage_step
@ -289,6 +290,6 @@ class Ci::PipelinesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
end end

View File

@ -30,19 +30,19 @@ class Ci::ProjectsController < Ci::BaseController
@file = interactor.result @file = interactor.result
render_result(1, "更新成功") render_result(1, "更新成功")
else else
render_error(interactor.error) tip_exception(interactor.error)
end end
end end
def activate def activate
return render_error('你还未认证') unless current_user.ci_certification? return tip_exception('你还未认证') unless current_user.ci_certification?
begin begin
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if @repo 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) @repo.activate!(@project)
return render_ok
else else
@repo = Ci::Repo.auto_create!(@ci_user, @project) @repo = Ci::Repo.auto_create!(@ci_user, @project)
@ci_user.update_column(:user_syncing, false) @ci_user.update_column(:user_syncing, false)
@ -55,12 +55,12 @@ class Ci::ProjectsController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
end end
def deactivate def deactivate
return render_error('该项目已经取消激活') if !@repo.repo_active? return tip_exception('该项目已经取消激活') if !@repo.repo_active?
@project.update_column(:open_devops, false) @project.update_column(:open_devops, false)
@repo.deactivate_repos! @repo.deactivate_repos!

View File

@ -20,14 +20,14 @@ class Ci::SecretsController < Ci::BaseController
if result["id"] if result["id"]
render_ok render_ok
else else
render_error(result["message"]) tip_exception(result["message"])
end end
else else
result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret
if result["id"] if result["id"]
render_ok render_ok
else else
render_error(result["message"]) tip_exception(result["message"])
end end
end 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 Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], {name: name}).delete_secret
render_ok render_ok
else else
render_error("参数名不能为空") tip_exception("参数名不能为空")
end end
rescue Exception => ex rescue Exception => ex
render_ok render_ok

View File

@ -50,7 +50,7 @@ class Ci::TemplatesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def update def update
@ -63,7 +63,7 @@ class Ci::TemplatesController < Ci::BaseController
) )
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
def destroy def destroy
@ -73,7 +73,7 @@ class Ci::TemplatesController < Ci::BaseController
end end
render_ok render_ok
rescue Exception => ex rescue Exception => ex
render_error(ex.message) tip_exception(ex.message)
end end
#======流水线模板查询=====# #======流水线模板查询=====#

View File

@ -160,9 +160,9 @@ module Ci::CloudAccountManageable
state = SecureRandom.hex(8) state = SecureRandom.hex(8)
# redirect_uri eg: # 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 # 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") # redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login")
clientId = client_id(oauth) # 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}" grant_url = "#{@cloud_account.drone_url}/login"
logger.info "[gitea] grant_url: #{grant_url}" logger.info "[gitea] grant_url: #{grant_url}"
conn = Faraday.new(url: grant_url) do |req| conn = Faraday.new(url: grant_url) do |req|
@ -179,7 +179,7 @@ module Ci::CloudAccountManageable
def drone_oauth_user!(url, state) def drone_oauth_user!(url, state)
logger.info "[drone] drone_oauth_user url: #{url}" 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.request :url_encoded
req.adapter Faraday.default_adapter req.adapter Faraday.default_adapter
req.headers["cookie"] = "_session_=#{SecureRandom.hex(28)}; _oauth_state_=#{state}" req.headers["cookie"] = "_session_=#{SecureRandom.hex(28)}; _oauth_state_=#{state}"
@ -188,8 +188,8 @@ module Ci::CloudAccountManageable
response = conn.get response = conn.get
logger.info "[drone] response headers: #{response.headers}" logger.info "[drone] response headers: #{response.headers}"
true # true
# response.headers['location'].include?('error') ? false : true response.headers['location'].include?('error') ? false : true
end end
private private

View File

@ -58,4 +58,32 @@ module RegisterHelper
end end
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 end

View File

@ -4,7 +4,7 @@ module RenderHelper
end end
def render_error(message = '') def render_error(message = '')
render json: { status: -1, message: message } render json: { status: status, message: message }
end end
def render_not_acceptable(message = '请求已拒绝') def render_not_acceptable(message = '请求已拒绝')

View File

@ -1,7 +1,7 @@
class NoticesController < ApplicationController class NoticesController < ApplicationController
def create def create
tip_exception("参数有误") if params["source"].blank? return tip_exception("参数有误") if params["source"].blank?
user_id = params[:user_id] user_id = params[:user_id]
if params["source"] == "CompetitionBegin" if params["source"] == "CompetitionBegin"
@ -13,9 +13,21 @@ class NoticesController < ApplicationController
elsif params["source"] == "CompetitionReview" elsif params["source"] == "CompetitionReview"
competition_id = params[:competition_id] competition_id = params[:competition_id]
SendTemplateMessageJob.perform_later('CompetitionReview', user_id, 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 else
tip_exception("#{params["source"]}未配置") tip_exception("#{params["source"]}未配置")
end end
render_ok render_ok
end end
private
def params_props
params.require(:notice).permit(:props)
end
end end

View File

@ -12,7 +12,7 @@ class OwnersController < ApplicationController
def show def show
@owner = Owner.find_by(login: params[:id]) || Owner.find_by(id: params[:id]) @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) if @owner.is_a?(Organization)
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition

View File

@ -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("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("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("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("resources")) if @project.has_menu_permission("resources") && @project.forge?
menu.append(menu_hash_by_name("activity")) menu.append(menu_hash_by_name("activity"))
menu.append(menu_hash_by_name("settings")) if user_is_admin && @project.forge? menu.append(menu_hash_by_name("settings")) if user_is_admin && @project.forge?

View File

@ -225,7 +225,8 @@ class RepositoriesController < ApplicationController
@path = Gitea.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/" @path = Gitea.gitea_config[:domain]+"/#{@owner.login}/#{@repository.identifier}/raw/branch/#{params[:ref]}/"
@readme = result[:status] === :success ? result[:body] : nil @readme = result[:status] === :success ? result[:body] : nil
@readme['content'] = decode64_content(@readme, @owner, @repository, params[:ref], @path) @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 rescue
render json: nil render json: nil
end end
@ -387,4 +388,4 @@ class RepositoriesController < ApplicationController
end end
end end
end end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,332 +1,399 @@
class UsersController < ApplicationController class UsersController < ApplicationController
include ApplicationHelper include ApplicationHelper
include Ci::DbConnectable include Ci::DbConnectable
before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard] 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 :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 :require_login, only: %i[me sync_user_info]
before_action :connect_to_ci_db, only: [:get_user_info] before_action :connect_to_ci_db, only: [:get_user_info]
before_action :convert_image!, only: [:update, :update_image] before_action :convert_image!, only: [:update, :update_image]
skip_before_action :check_sign, only: [:attachment_show] 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?) def connect_to_ci_db(options={})
return if !(current_user && !current_user.is_a?(AnonymousUser) && current_user.devops_certification?)
end return
if current_user.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE end
connect_to_trustie_ci_database(options) if current_user.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE
else connect_to_trustie_ci_database(options)
connect_to_ci_database(options) else
end connect_to_ci_database(options)
end end
end
def list
scope = User.active.recent.like(params[:search]).includes(:user_extension) def list
@total_count = scope.size scope = User.active.recent.like(params[:search]).includes(:user_extension)
@users = paginate(scope) @total_count = scope.size
end @users = paginate(scope)
end
def show
#待办事项,现在未做 def show
if User.current.admin? || User.current.login == @user.login #待办事项,现在未做
@waiting_applied_messages = @user.applied_messages.waiting if User.current.admin? || User.current.login == @user.login
@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 @waiting_applied_messages = @user.applied_messages.waiting
@common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common
#@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size @common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common
@undo_events = @common_applied_transfer_projects.size + @common_applied_projects.size #@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size
else @undo_events = @common_applied_transfer_projects.size + @common_applied_projects.size
@waiting_applied_messages = AppliedMessage.none else
@common_applied_transfer_projects = AppliedTransferProject.none @waiting_applied_messages = AppliedMessage.none
@common_applied_projects = AppliedProject.none @common_applied_transfer_projects = AppliedTransferProject.none
@undo_events = 0 @common_applied_projects = AppliedProject.none
end @undo_events = 0
#用户的组织数量 end
# @user_composes_count = @user.composes.size #用户的组织数量
@user_composes_count = 0 # @user_composes_count = @user.composes.size
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_composes_count = 0
@user_org_count = user_organizations.size 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")
normal_projects = Project.members_projects(@user.id).to_sql @user_org_count = user_organizations.size
org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql normal_projects = Project.members_projects(@user.id).to_sql
projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql
user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
@projects_common_count = user_projects.common.size user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible
@projects_mirrior_count = user_projects.mirror.size @projects_common_count = user_projects.common.size
@projects_sync_mirrior_count = user_projects.sync_mirror.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 Cache::V2::OwnerCommonService.new(@user.id).read
end
def watch_users
watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc") def watch_users
if params[:search].present? watchers = Watcher.watching_users(@user.id).includes(:user).order("watchers.created_at desc")
search_user_ids = User.where(id: watchers.pluck(:watchable_id)).like(params[:search]).pluck(:id) if params[:search].present?
watchers = watchers.where(watchable_id: search_user_ids) search_user_ids = User.where(id: watchers.pluck(:watchable_id)).like(params[:search]).pluck(:id)
end watchers = watchers.where(watchable_id: search_user_ids)
@watchers_count = watchers.size end
@watchers = paginate(watchers) @watchers_count = watchers.size
end @watchers = paginate(watchers)
end
def fan_users
watchers = @user.watchers.includes(:user).order("watchers.created_at desc") def fan_users
watchers = watchers.joins(:user).merge(User.like(params[:search])) watchers = @user.watchers.includes(:user).order("watchers.created_at desc")
@watchers_count = watchers.size watchers = watchers.joins(:user).merge(User.like(params[:search]))
@watchers = paginate(watchers) @watchers_count = watchers.size
end @watchers = paginate(watchers)
end
def hovercard
end def hovercard
end
def update
return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) def update
return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id])
Util.write_file(@image, avatar_path(@user)) if user_params[:image].present? return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
@user.attributes = user_params.except(:image) Util.write_file(@image, avatar_path(@user)) if user_params[:image].present?
unless @user.save @user.attributes = user_params.except(:image)
render_error(-1, @user.errors.full_messages.join(", ")) unless @user.save
end render_error(-1, @user.errors.full_messages.join(", "))
end end
end
def update_image
return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) def update_image
return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) 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: '头像修改成功'}) Util.write_file(@image, avatar_path(@user))
rescue Exception => e return render_ok({message: '头像修改成功'})
uid_logger_error(e.message) rescue Exception => e
render_error(-1, '头像修改失败!') uid_logger_error(e.message)
end 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]) def get_image
return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) 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 redirect_to Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(@user).to_s
end
def me
@user = current_user def me
end @user = current_user
end
# 贴吧获取用户信接口
def get_user_info # 贴吧获取用户信接口
begin def get_user_info
@user = current_user begin
begin @user = current_user
result = Notice::Read::CountService.call(current_user.id) begin
@message_unread_total = result.nil? ? 0 : result[2]["unread_notification"] result = Notice::Read::CountService.call(current_user.id)
rescue @message_unread_total = result.nil? ? 0 : result[2]["unread_notification"]
@message_unread_total = 0 rescue
end @message_unread_total = 0
# TODO 等消息上线再打开注释 end
#@tidding_count = unviewed_tiddings(current_user) if current_user.present? # TODO 等消息上线再打开注释
rescue Exception => e #@tidding_count = unviewed_tiddings(current_user) if current_user.present?
uid_logger_error(e.message) rescue Exception => e
missing_template uid_logger_error(e.message)
end missing_template
end
end
end
def attachment_show
file_name = params[:file_name] def attachment_show
path = params[:path] || file_storage_directory file_name = params[:file_name]
send_file "#{path}/#{file_name}", :filename => "#{file_name}", path = params[:path] || file_storage_directory
:type => 'game', send_file "#{path}/#{file_name}", :filename => "#{file_name}",
:disposition => 'attachment' #inline can open in browser :type => 'game',
end :disposition => 'attachment' #inline can open in browser
end
def html_show
@contents = File.read("#{params[:path]}") def html_show
respond_to do |format| @contents = File.read("#{params[:path]}")
format.html {render :layout => false} respond_to do |format|
end format.html {render :layout => false}
end end
end
# Redo: 消息总数缓存
def get_navigation_info # Redo: 消息总数缓存
# @old_domain = edu_setting('old_edu_host') def get_navigation_info
# @user = current_user # @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 # # 新消息数
# # @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) # @user_url = "/users/#{@user.login}"
# @auth = User.current.ec_school.present? ? "#{@old_domain}/ecs/department?school_id=#{User.current.ec_school}" : nil # @career = Career.where(status: true).order("created_at asc").pluck(:id, :name)
end # @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) def reply_message
message.user_id = current_user.id message = JournalsForMessage.new(reply_message_params)
message.save! message.user_id = current_user.id
message.save!
render_ok(id: message.id)
end render_ok(id: message.id)
end
# 搜索用户具有管理员角色的项目
def search_user_projects # 搜索用户具有管理员角色的项目
projects = Project.where.not(status: 9) 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 }) 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? search = params[:search].to_s.strip
projects = projects.where('projects.name LIKE ?', "%#{search}%") if search.present?
@projects = projects.select(:id, :name)
end @projects = projects.select(:id, :name)
end
#TODO 个人主页信息forge上弃用-hs, 0602
def homepage_info #TODO 个人主页信息forge上弃用-hs, 0602
#待办事项,现在未做 def homepage_info
@undo_events = 10 #待办事项,现在未做
#用户的组织数量 @undo_events = 10
# @user_composes_count = @user.composes.size #用户的组织数量
@user_composes_count = 10 # @user_composes_count = @user.composes.size
end @user_composes_count = 10
end
def brief_introduction
content = params[:content].to_s.strip def brief_introduction
content = params[:content].to_s.strip
current_user.user_extension.update!(brief_introduction: content)
current_user.user_extension.update!(brief_introduction: content)
render_ok
end render_ok
end
def attendance
attendance = Users::AttendanceService.call(current_user) def attendance
render_ok(grade: current_user.grade, next_gold: attendance.next_gold) attendance = Users::AttendanceService.call(current_user)
rescue Users::AttendanceService::Error => ex render_ok(grade: current_user.grade, next_gold: attendance.next_gold)
render_error(ex.message) rescue Users::AttendanceService::Error => ex
end render_error(ex.message)
end
# 其他平台登录后必须将token同步到forge平台实现sso登录功能
def sync_token # 其他平台登录后必须将token同步到forge平台实现sso登录功能
return render_error('未找相关用户!') unless @user def sync_token
return render_error('未找相关用户!') unless @user
token = Token.get_or_create_permanent_login_token(@user, 'autologin')
token.update_column(:value, params[:token]) token = Token.get_or_create_permanent_login_token(@user, 'autologin')
render_ok token.update_column(:value, params[:token])
end render_ok
end
def trustie_related_projects
projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc") def trustie_related_projects
projects_json = [] projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc")
domain_url = EduSetting.get('host_name') projects_json = []
if projects.present? domain_url = EduSetting.get('host_name')
projects.each do |p| if projects.present?
project_url = "/#{p.owner.login}/#{p.identifier}" projects.each do |p|
pj = { project_url = "/#{p.owner.login}/#{p.identifier}"
id: p.id, pj = {
name: p.name, id: p.id,
is_public: p.is_public, name: p.name,
updated_on: p.updated_on.strftime("%Y-%m-%d"), is_public: p.is_public,
status: p.status, updated_on: p.updated_on.strftime("%Y-%m-%d"),
is_member: p.member?(current_user.try(:id)), status: p.status,
owner: { is_member: p.member?(current_user.try(:id)),
name: p.owner.try(:show_real_name), owner: {
login: p.owner.login 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, members_count: p&.members.size,
commits_count: p&.project_score&.changeset_num.to_i, issues_count: p.issues_count - p.pull_requests_count,
http_url: domain_url + project_url, commits_count: p&.project_score&.changeset_num.to_i,
http_collaborator_url: domain_url + project_url + "/setting/collaborator", http_url: domain_url + project_url,
http_issues_url: domain_url + project_url + "/issues", http_collaborator_url: domain_url + project_url + "/setting/collaborator",
http_commits_url: domain_url + project_url + "/commits", http_issues_url: domain_url + project_url + "/issues",
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)) : {} 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 projects_json.push(pj)
end end
Rails.logger.info("==========projects_json========+########{projects_json}") end
render json: { projects: projects_json.present? ? projects_json : {} } Rails.logger.info("==========projects_json========+########{projects_json}")
end render json: { projects: projects_json.present? ? projects_json : {} }
end
def trustie_projects
user_id = User.select(:id, :login).where(login: params[:login])&.first&.id def trustie_projects
projects = Project.visible 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 })
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? 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 } projects = projects.select(:id, :name).limit(10).as_json
end render json: { projects: projects }
end
def projects
is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == @user.id) def projects
scope = Projects::ListMyQuery.call(params, @user,is_current_admin_user) is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
@total_count = scope.size scope = Projects::ListMyQuery.call(params, @user,is_current_admin_user)
@projects = paginate(scope) @total_count = scope.size
end @projects = paginate(scope)
end
# TODO 其他平台登录时同步修改gitea平台对应用户的密码
# 该方法主要用于别的平台初次部署对接forge平台同步用户后gitea平台对应的用户密码与forge平台用户密码不一致是问题 # TODO 其他平台登录时同步修改gitea平台对应用户的密码
def sync_gitea_pwd # 该方法主要用于别的平台初次部署对接forge平台同步用户后gitea平台对应的用户密码与forge平台用户密码不一致是问题
return render_error("未找到相关的用户") if @user.blank? 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('同步失败!') flag = sync_pwd_to_gitea!(@user, {password: params[:password].to_s})
end flag ? render_ok : render_error('同步失败!')
end
# TODO
# 同步trusite平台用户的salt信息只需同步一次同步完成后该方法可以删除 # TODO
def sync_salt # 同步trusite平台用户的salt信息只需同步一次同步完成后该方法可以删除
user = User.find_by_login params[:login] def sync_salt
return if user.blank? user = User.find_by_login params[:login]
user.update_column(:salt, params[:salt]) return if user.blank?
render_ok user.update_column(:salt, params[:salt])
end render_ok
end
def sync_user_info
user = User.find_by_login params[:login] def sync_user_info
return render_forbidden unless user === current_user user = User.find_by_login params[:login]
return render_forbidden unless user === current_user
sync_params = {
email: params[:email], sync_params = {
password: params[:password] email: params[:email],
} password: params[:password]
}
Users::UpdateInfoForm.new(sync_params.merge(login: params[:login])).validate!
Users::UpdateInfoForm.new(sync_params.merge(login: params[:login])).validate!
interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params)
if interactor.success? interactor = Gitea::User::UpdateInteractor.call(user.login, sync_params)
user.update!(password: params[:password], mail: params[:email], status: User::STATUS_ACTIVE) if interactor.success?
render_ok user.update!(password: params[:password], mail: params[:email], status: User::STATUS_ACTIVE)
else render_ok
render_error(interactor.error) else
end render_error(interactor.error)
end end
end
private
def load_user def email_search
@user = User.find_by_login(params[:id]) || User.find_by(id: params[:id]) return render_error('请输入email') if params[:email].blank?
end @user = User.find_by(mail: params[:email])
end
def user_params
params.require(:user).permit(:nickname, :image, private
user_extension_attributes: [ def load_user
:gender, :location, :location_city, @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id])
:occupation, :technical_title, end
:school_id, :department_id, :province, :city,
:custom_department, :identity, :student_id, :description, def user_params
:show_super_description, :super_description, params.require(:user).permit(:nickname, :image,
:show_email, :show_location, :show_department] user_extension_attributes: [
) :gender, :location, :location_city,
end :occupation, :technical_title,
:school_id, :department_id, :province, :city,
def reply_message_params :custom_department, :identity, :student_id, :description,
normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? || :show_super_description, :super_description,
params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil? :show_email, :show_location, :show_department]
params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id) )
end end
def check_user_exist def reply_message_params
return if @user.present? normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? ||
render_not_found params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil?
end params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id)
end
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

View File

@ -16,6 +16,7 @@ includes:
- users - users
- projects - projects
- repositories - repositories
- traces
- pulls - pulls
- issues - issues
- organizations - organizations

View File

@ -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示例: > 返回的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", "created_at": "2021-04-26 09:54",
"time_ago": "1分钟前" "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"
}
``` ```

View File

@ -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"
}
```
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
## 代码分析结果列表
查询项目下代码分析的结果
> 示例:
```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 | 每页数量 |
### 返回字段说明(暂缺)
<!-- 参数 | 类型 | 字段说明
--------- | ----------- | -----------
total_count |int |总数 |
public_keys.id |int |ID|
public_keys.name |string|密钥标题|
public_keys.content |string|密钥内容|
public_keys.fingerprint |string|密钥标识|
public_keys.created_time |string|密钥创建时间| -->
> 返回的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": ""
}
]
}
```
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
## 新建分析
用户选择仓库分支进行代码分析的接口
> 示例:
```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"
}
```
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
## 重新扫描
对代码分析结果进行再次分析
> 示例:
```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"
}
```
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
## 下载报告
把代码分析的结果下载到本地
> 示例:
```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"
}
```
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>

View File

@ -4,7 +4,7 @@ class Gitea::User::UpdateForm
attr_accessor :username, :email, :admin, :allow_create_organization, :allow_git_hook, :allow_import_local, 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, :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 :username, presence: true
validates :email, presence: true, format: { with: EMAIL_REGEX, multiline: true } validates :email, presence: true, format: { with: EMAIL_REGEX, multiline: true }

View File

@ -3,28 +3,40 @@ module Register
include ActiveModel::Model include ActiveModel::Model
private private
def check_login(login) def check_login(login, user=nil)
login = strip(login) login = strip(login)
raise LoginError, "登录名格式有误" unless login =~ CustomRegexp::LOGIN raise LoginError, "登录名格式有误" unless login =~ CustomRegexp::LOGIN
login_exist = Owner.exists?(login: login) || ReversedKeyword.check_exists?(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 end
def check_mail(mail) def check_mail(mail, user=nil)
mail = strip(mail) mail = strip(mail)
raise EmailError, "邮件格式有误" unless mail =~ CustomRegexp::EMAIL raise EmailError, "邮件格式有误" unless mail =~ CustomRegexp::EMAIL
mail_exist = Owner.exists?(mail: mail) 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 end
def check_phone(phone) def check_phone(phone, user=nil)
phone = strip(phone) phone = strip(phone)
raise PhoneError, "手机号格式有误" unless phone =~ CustomRegexp::PHONE raise PhoneError, "手机号格式有误" unless phone =~ CustomRegexp::PHONE
phone_exist = Owner.exists?(phone: 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 end
end end

View File

@ -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) 2email(邮箱) 3phone(手机号)
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

View File

@ -1,486 +1,486 @@
# 所有的方法请按首字母的顺序依次列出 # 所有的方法请按首字母的顺序依次列出
module ApplicationHelper module ApplicationHelper
include Gitlink::I18n include Gitlink::I18n
include GitHelper include GitHelper
ONE_MINUTE = 60 * 1000 ONE_MINUTE = 60 * 1000
ONE_HOUR = 60 * ONE_MINUTE ONE_HOUR = 60 * ONE_MINUTE
ONE_DAY = 24 * ONE_HOUR ONE_DAY = 24 * ONE_HOUR
ONE_MONTH = 30 * ONE_DAY ONE_MONTH = 30 * ONE_DAY
ONE_YEAR = 12 * ONE_MONTH ONE_YEAR = 12 * ONE_MONTH
# 全局参数配置 # 全局参数配置
def edu_setting name def edu_setting name
EduSetting.get(name) EduSetting.get(name)
end end
# xss共计问题 # xss共计问题
def content_safe content def content_safe content
tags = %w( tags = %w(
a abbr b bdo blockquote br caption cite code col colgroup dd del dfn dl 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 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 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 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) attributes = %w(href src width height alt cite datetime title class name xml:lang abbr style)
sanitize content, tags: tags, attributes: attributes sanitize content, tags: tags, attributes: attributes
end end
def graduation_navigation graduation def graduation_navigation graduation
graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务" graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务"
end end
def graduation_navigation_id course def graduation_navigation_id course
course.course_modules.find_by(module_type: "graduation").try(:id) course.course_modules.find_by(module_type: "graduation").try(:id)
end end
# git用户 # git用户
# git用户命名规则login+"@educoder.net" # git用户命名规则login+"@educoder.net"
def git_username(email) def git_username(email)
User.find_by_mail(email) || User.find_by_login(email.split("@").first) User.find_by_mail(email) || User.find_by_login(email.split("@").first)
end end
# 不同的类型扩展不同的目录 # 不同的类型扩展不同的目录
def relative_path def relative_path
"avatars" "avatars"
end end
def replace_bytes_to_b(size_string) def replace_bytes_to_b(size_string)
return size_string.gsub("Bytes", "B").gsub("bytes", "B").gsub("字节", "B") return size_string.gsub("Bytes", "B").gsub("bytes", "B").gsub("字节", "B")
end end
def storage_path def storage_path
File.join(Rails.root, "public", "images", relative_path) File.join(Rails.root, "public", "images", relative_path)
end end
# 推荐实训 # 推荐实训
def recommend_shixun(shixun) def recommend_shixun(shixun)
tag_repertoire_id = shixun.tag_repertoires.first.present? ? shixun.tag_repertoires.first.try(:id) : 0 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 = ShixunTagRepertoire.where("tag_repertoire_id = #{tag_repertoire_id} and
shixun_id != #{shixun.id}").pluck(:shixun_id) shixun_id != #{shixun.id}").pluck(:shixun_id)
shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",") shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",")
Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id 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) in(#{shixun_id})").unhidden.publiced.order("homepage_show asc, myshixuns_count desc").limit(3)
end end
# shixun开启挑战对应的行为名及url # shixun开启挑战对应的行为名及url
def task_operation_url current_myshixun, shixun def task_operation_url current_myshixun, shixun
if current_myshixun.blank? if current_myshixun.blank?
name = shixun.status == 0 ? "模拟实战" : "开启挑战" name = shixun.status == 0 ? "模拟实战" : "开启挑战"
url = "/shixuns/#{shixun.identifier}/shixun_exec" url = "/shixuns/#{shixun.identifier}/shixun_exec"
else else
identifier = current_myshixun.current_task(current_myshixun.games).try(:identifier) identifier = current_myshixun.current_task(current_myshixun.games).try(:identifier)
if current_myshixun.status == 1 if current_myshixun.status == 1
name = "查看实战" name = "查看实战"
else else
name = "继续挑战" name = "继续挑战"
end end
url = identifier url = identifier
end end
[name, url] [name, url]
end end
# 获取当前时间 # 获取当前时间
def time_from_now(time) def time_from_now(time)
if String === time if String === time
time = Time.parse(time) time = Time.parse(time)
end end
lastUpdateTime = time.to_i*1000 lastUpdateTime = time.to_i*1000
currentTime = Time.now.to_i*1000 currentTime = Time.now.to_i*1000
timePassed = currentTime - lastUpdateTime timePassed = currentTime - lastUpdateTime
timeIntoFormat = 0 timeIntoFormat = 0
updateAtValue = "" updateAtValue = ""
if timePassed < 0 if timePassed < 0
updateAtValue = "刚刚" updateAtValue = "刚刚"
elsif timePassed < ONE_MINUTE elsif timePassed < ONE_MINUTE
updateAtValue = "1分钟前" updateAtValue = "1分钟前"
elsif timePassed < ONE_HOUR elsif timePassed < ONE_HOUR
timeIntoFormat = timePassed / ONE_MINUTE timeIntoFormat = timePassed / ONE_MINUTE
updateAtValue = timeIntoFormat.to_s + "分钟前" updateAtValue = timeIntoFormat.to_s + "分钟前"
elsif (timePassed < ONE_DAY) elsif (timePassed < ONE_DAY)
timeIntoFormat = timePassed / ONE_HOUR timeIntoFormat = (timePassed.to_f / ONE_HOUR).ceil
updateAtValue = timeIntoFormat.to_s + "小时前" updateAtValue = timeIntoFormat.to_s + "小时前"
elsif (timePassed < ONE_MONTH) elsif (timePassed < ONE_MONTH)
timeIntoFormat = timePassed / ONE_DAY timeIntoFormat = (timePassed.to_f / ONE_DAY).ceil
updateAtValue = timeIntoFormat.to_s + "天前" updateAtValue = timeIntoFormat.to_s + "天前"
elsif (timePassed < ONE_YEAR) elsif (timePassed < ONE_YEAR)
timeIntoFormat = timePassed / ONE_MONTH timeIntoFormat = (timePassed.to_f / ONE_MONTH).ceil
updateAtValue = timeIntoFormat.to_s + "个月前" updateAtValue = timeIntoFormat.to_s + "个月前"
else else
timeIntoFormat = timePassed / ONE_YEAR timeIntoFormat = timePassed / ONE_YEAR
updateAtValue = timeIntoFormat.to_s + "年前" updateAtValue = timeIntoFormat.to_s + "年前"
end end
updateAtValue updateAtValue
end end
# 计算到结束还有多长时间 **天**小时**分 # 计算到结束还有多长时间 **天**小时**分
def how_much_time(time) def how_much_time(time)
if time.nil? || time < Time.now #6.21 -hs 增加小于time.now if time.nil? || time < Time.now #6.21 -hs 增加小于time.now
'' ''
else 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)).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)).to_s + " 小时 "
result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + "" result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + ""
end end
end end
def format_time(time) def format_time(time)
time.present? ? time.strftime("%Y-%m-%d %H:%M") : '' time.present? ? time.strftime("%Y-%m-%d %H:%M") : ''
end end
# 用户图像url如果不存在的话source为匿名用户即默认使用匿名用户图像 # 用户图像url如果不存在的话source为匿名用户即默认使用匿名用户图像
def url_to_avatar(source) def url_to_avatar(source)
if File.exist?(disk_filename(source&.class, source&.id)) if File.exist?(disk_filename(source&.class, source&.id))
ctime = File.ctime(disk_filename(source.class, source.id)).to_i ctime = File.ctime(disk_filename(source.class, source.id)).to_i
if %w(User Organization).include?(source.class.to_s) if %w(User Organization).include?(source.class.to_s)
File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
else else
File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
end end
elsif source.class.to_s == 'User' elsif source.class.to_s == 'User'
source.get_letter_avatar_url source.get_letter_avatar_url
end end
end end
def url_to_avatar_with_platform_url(source) def url_to_avatar_with_platform_url(source)
platform_url = Rails.application.config_for(:configuration)['platform_url'] platform_url = Rails.application.config_for(:configuration)['platform_url']
if platform_url if platform_url
return Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(source).to_s return Rails.application.config_for(:configuration)['platform_url'] + "/" + url_to_avatar(source).to_s
else else
return url_to_avatar(source).to_s return url_to_avatar(source).to_s
end end
end end
# 主页banner图 # 主页banner图
def banner_img(source_type) def banner_img(source_type)
if File.exist?(disk_filename(source_type, "banner")) if File.exist?(disk_filename(source_type, "banner"))
ctime = File.ctime(disk_filename(source_type, "banner")).to_i ctime = File.ctime(disk_filename(source_type, "banner")).to_i
File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}" File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}"
end end
end end
def disk_filename(source_type,source_id,image_file=nil) def disk_filename(source_type,source_id,image_file=nil)
File.join(storage_path, "#{source_type}", "#{source_id}") File.join(storage_path, "#{source_type}", "#{source_id}")
end end
def disk_auth_filename(source_type, source_id, type) def disk_auth_filename(source_type, source_id, type)
File.join(storage_path, "#{source_type}", "#{source_id}#{type}") File.join(storage_path, "#{source_type}", "#{source_id}#{type}")
end end
def disk_real_name_auth_filename(source_id) def disk_real_name_auth_filename(source_id)
disk_auth_filename('UserAuthentication', source_id, 'ID') disk_auth_filename('UserAuthentication', source_id, 'ID')
end end
def auth_file_url(source_type, source_id, type) def auth_file_url(source_type, source_id, type)
File.join('/images', relative_path, source_type, "#{source_id}#{type}") File.join('/images', relative_path, source_type, "#{source_id}#{type}")
end end
def real_name_auth_file_url(source_id) def real_name_auth_file_url(source_id)
auth_file_url('UserAuthentication', source_id, 'ID') auth_file_url('UserAuthentication', source_id, 'ID')
end end
def disk_professional_auth_filename(source_id) def disk_professional_auth_filename(source_id)
disk_auth_filename('UserAuthentication', source_id, 'PRO') disk_auth_filename('UserAuthentication', source_id, 'PRO')
end end
def professional_auth_file_url(source_id) def professional_auth_file_url(source_id)
auth_file_url('UserAuthentication', source_id, 'PRO') auth_file_url('UserAuthentication', source_id, 'PRO')
end end
def shixun_url_to_avatar(shixun) def shixun_url_to_avatar(shixun)
if File.exist?(disk_filename(shixun.class, shixun.id)) if File.exist?(disk_filename(shixun.class, shixun.id))
File.join("images/#{relative_path}", "#{shixun.class}", "#{shixun.id}") File.join("images/#{relative_path}", "#{shixun.class}", "#{shixun.id}")
else else
File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg") File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg")
end end
end end
# 选用实训的学校情况 # 选用实训的学校情况
def school_user_detail shixun def school_user_detail shixun
user_ids = shixun.myshixuns.map(&:user_id).uniq # 走缓存取数据 user_ids = shixun.myshixuns.map(&:user_id).uniq # 走缓存取数据
school_ids = UserExtension.where(user_id:user_ids).pluck(:school_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_names = School.where(id: school_ids[0..1]).pluck(:name)
school_size = school_ids.size school_size = school_ids.size
str = school_size > 0 ? "#{school_names.join("")}#{school_size}" : "0所" str = school_size > 0 ? "#{school_names.join("")}#{school_size}" : "0所"
end end
# 普通/分组 作业作品状态数组 # 普通/分组 作业作品状态数组
def student_work_status homework, user_id, course, work def student_work_status homework, user_id, course, work
status = [] status = []
homework_setting = homework.homework_group_setting user_id, true homework_setting = homework.homework_group_setting user_id, true
work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id) work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id)
late_time = homework.late_time || course.end_date late_time = homework.late_time || course.end_date
if course.is_end && work && work.work_status > 0 if course.is_end && work && work.work_status > 0
status << "查看作品" status << "查看作品"
elsif !course.is_end elsif !course.is_end
if homework_setting.publish_time && homework_setting.publish_time < Time.now if homework_setting.publish_time && homework_setting.publish_time < Time.now
# 作业未截止时 # 作业未截止时
if homework_setting.end_time > Time.now if homework_setting.end_time > Time.now
if homework.homework_type == "group" && homework.homework_detail_group.base_on_project if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
if work.project_id.nil? || work.project_id == 0 if work.project_id.nil? || work.project_id == 0
status << "创建项目" status << "创建项目"
status << "关联项目" status << "关联项目"
elsif work.work_status == 0 elsif work.work_status == 0
status << "取消关联" status << "取消关联"
status << "提交作品" status << "提交作品"
else else
status << "修改作品" status << "修改作品"
end end
else else
if work.work_status == 0 if work.work_status == 0
status << "提交作品" status << "提交作品"
else else
status << "修改作品" status << "修改作品"
end end
end end
# 补交阶段 # 补交阶段
elsif homework.allow_late && (late_time.nil? || late_time > Time.now) elsif homework.allow_late && (late_time.nil? || late_time > Time.now)
if homework.homework_type == "group" && homework.homework_detail_group.base_on_project if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
if work.project_id.nil? || work.project_id == 0 if work.project_id.nil? || work.project_id == 0
status << "创建项目" status << "创建项目"
status << "关联项目" status << "关联项目"
elsif work.work_status == 0 elsif work.work_status == 0
status << "取消关联" status << "取消关联"
status << "补交作品" status << "补交作品"
else else
status << "补交附件" status << "补交附件"
status << "查看作品" status << "查看作品"
end end
else else
if work.work_status == 0 if work.work_status == 0
status << "补交作品" status << "补交作品"
else else
status << "补交附件" status << "补交附件"
status << "查看作品" status << "查看作品"
end end
end end
# 匿评阶段 # 匿评阶段
elsif work.work_status != 0 elsif work.work_status != 0
if homework.homework_detail_manual.comment_status == 3 if homework.homework_detail_manual.comment_status == 3
work_ids = homework.student_works.has_committed.pluck(:id) work_ids = homework.student_works.has_committed.pluck(:id)
if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0 if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0
status << "匿评作品" status << "匿评作品"
end end
end end
status << "查看作品" status << "查看作品"
end end
end end
end end
end end
def commit_des_status work, homework def commit_des_status work, homework
status = [] status = []
homework_setting = homework.homework_group_setting work.user_id homework_setting = homework.homework_group_setting work.user_id
# 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结 # 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结
if homework_setting.publish_time && homework_setting.publish_time < Time.now && work.work_status > 0 && 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_setting.end_time && homework_setting.end_time > Time.now) ||
(homework.allow_late && homework.late_time && homework.late_time > Time.now)) (homework.allow_late && homework.late_time && homework.late_time > Time.now))
work.description.present? ? status << "修改总结" : status << "提交总结" work.description.present? ? status << "修改总结" : status << "提交总结"
end end
end end
def download_url attachment,options={} def download_url attachment,options={}
attachment_path(attachment,options) attachment_path(attachment,options)
end end
# 耗时:天、小时、分、秒 # 耗时:天、小时、分、秒
# 小于1分钟则不显示 # 小于1分钟则不显示
def game_spend_time time def game_spend_time time
day = time / 86400 day = time / 86400
hour = time % (24*60*60) / (60*60) hour = time % (24*60*60) / (60*60)
min = time % (24*60*60) % (60*60) / 60 min = time % (24*60*60) % (60*60) / 60
sec = time % (24*60*60) % (60*60) % 60 sec = time % (24*60*60) % (60*60) % 60
if day < 1 if day < 1
if hour < 1 if hour < 1
if min < 1 if min < 1
if sec < 1 if sec < 1
time = "--" time = "--"
else else
time = "#{sec}" time = "#{sec}"
end end
else else
time = "#{min}分钟 #{sec}" time = "#{min}分钟 #{sec}"
end end
else else
time = "#{hour}小时 #{min}分钟 #{sec}" time = "#{hour}小时 #{min}分钟 #{sec}"
end end
else else
time = "#{day}#{hour}小时 #{min}分钟 #{sec}" time = "#{day}#{hour}小时 #{min}分钟 #{sec}"
end end
return time return time
end end
def absolute_path(file_path) def absolute_path(file_path)
file_root_directory + File.join(edu_setting('attachment_folder'), file_path) file_root_directory + File.join(edu_setting('attachment_folder'), file_path)
end end
def file_root_directory def file_root_directory
Rails.root.to_s Rails.root.to_s
end end
def file_storage_directory def file_storage_directory
file_root_directory + edu_setting('attachment_folder') file_root_directory + edu_setting('attachment_folder')
end end
def local_path(file) def local_path(file)
File.join(file.disk_directory, file.disk_filename) File.join(file.disk_directory, file.disk_filename)
end end
def update_downloads(file) def update_downloads(file)
file.update_attributes(:downloads => file.downloads + 1) file.update_attributes(:downloads => file.downloads + 1)
end end
# 项目信息, # 项目信息,
def project_info work, current_user, course_identity def project_info work, current_user, course_identity
project = work.project project = work.project
if project if project
if project.status == 9 if project.status == 9
{id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count} {id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count}
else else
project_score = project.project_score project_score = project.project_score
if project.is_public || current_user.manager_of_project?(project) || course_identity < Course::STUDENT 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, {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, 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} attachment_score: project_score.attachment_score, message_score: project_score.message_score}
else else
{id: -1, name: "#{project.name}", title: "该项目是私有的", author: project.creator, member_count: project.project_members.count, {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, 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} attachment_score: project_score.attachment_score, message_score: project_score.message_score}
end end
end end
else else
{id: -1, name: "--", title: "--"} {id: -1, name: "--", title: "--"}
end end
end end
def message_content(content) def message_content(content)
content = (strip_html content).strip content = (strip_html content).strip
content = content.gsub(/\s+/, " ") content = content.gsub(/\s+/, " ")
if content.gsub(" ", "") == "" if content.gsub(" ", "") == ""
content = "[非文本消息]" content = "[非文本消息]"
end end
content content
end end
def strip_html(text, len=0, endss="...") def strip_html(text, len=0, endss="...")
ss = "" ss = ""
if !text.nil? && text.length>0 if !text.nil? && text.length>0
ss=text.gsub(/<\/?.*?>/, '').strip ss=text.gsub(/<\/?.*?>/, '').strip
ss = ss.gsub(/&nbsp;*/, '') ss = ss.gsub(/&nbsp;*/, '')
ss = ss.gsub(/\r\n/,'') #新增 ss = ss.gsub(/\r\n/,'') #新增
ss = ss.gsub(/\n/,'') #新增 ss = ss.gsub(/\n/,'') #新增
if len > 0 && ss.length > len if len > 0 && ss.length > len
ss = ss[0, len] + endss ss = ss[0, len] + endss
elsif len > 0 && ss.length <= len elsif len > 0 && ss.length <= len
ss = ss ss = ss
#ss = truncate(ss, :length => len) #ss = truncate(ss, :length => len)
end end
end end
ss ss
end end
def strip_export_title(content) def strip_export_title(content)
con_ = "" con_ = ""
if content.length > 0 if content.length > 0
con_ = strip_tags(content) con_ = strip_tags(content)
con_ = con_.gsub(/\r\n/,'').gsub(/&nbsp;*/, '').strip con_ = con_.gsub(/\r\n/,'').gsub(/&nbsp;*/, '').strip
end end
con_ con_
end end
def pdf_load_sources(*arg) def pdf_load_sources(*arg)
arr = arg.map do |path| arr = arg.map do |path|
content_tag(:script) do content_tag(:script) do
File.open(Rails.root.join('public', path)).read.to_s.html_safe File.open(Rails.root.join('public', path)).read.to_s.html_safe
end end
end end
raw arr.join('') raw arr.join('')
end end
# 导出pdf时转化markdown为html # 导出pdf时转化markdown为html
def to_markdown(text,origin_url) def to_markdown(text,origin_url)
return nil if text.blank? return nil if text.blank?
options = { options = {
:autolink => true, :autolink => true,
:no_intra_emphasis => true, :no_intra_emphasis => true,
:fenced_code_blocks => true, :fenced_code_blocks => true,
:lax_html_blocks => true, :lax_html_blocks => true,
:strikethrough => true, :strikethrough => true,
:superscript => false, :superscript => false,
:tables => true :tables => true
} }
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options) markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options)
m_t = markdown.render(text) m_t = markdown.render(text)
m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t m_t&.include?("src=\"") ? m_t&.gsub("src=\"","src=\"#{origin_url}") : m_t
end end
def shixun_status_class(shixun) def shixun_status_class(shixun)
case shixun.status case shixun.status
when 0 then 'text-info' when 0 then 'text-info'
when 1 then 'text-warning' when 1 then 'text-warning'
when 2 then 'text-success' when 2 then 'text-success'
when 3 then 'text-secondary' when 3 then 'text-secondary'
end end
end end
def render_unix_time(date) def render_unix_time(date)
date.to_time.to_i date.to_time.to_i
end end
def find_user_by_login(login) def find_user_by_login(login)
User.find_by_login login User.find_by_login login
end end
def find_user_by_login_and_mail(login, mail) def find_user_by_login_and_mail(login, mail)
User.find_by(login: login, mail: mail) User.find_by(login: login, mail: mail)
end end
def find_user_by_gitea_uid(gitea_uid) def find_user_by_gitea_uid(gitea_uid)
User.find_by(gitea_uid: gitea_uid) User.find_by(gitea_uid: gitea_uid)
end end
def find_user_in_redis_cache(login, email) def find_user_in_redis_cache(login, email)
$redis_cache.hgetall("v2-owner-common:#{login}-#{email}") $redis_cache.hgetall("v2-owner-common:#{login}-#{email}")
end end
def find_user_in_redis_cache_by_id(id) def find_user_in_redis_cache_by_id(id)
$redis_cache.hgetall("v2-owner-common:#{id}") $redis_cache.hgetall("v2-owner-common:#{id}")
end end
def render_base64_decoded(str) def render_base64_decoded(str)
return nil if str.blank? return nil if str.blank?
Base64.decode64 str Base64.decode64 str
end end
def render_admin_statistics_item def render_admin_statistics_item
url = Rails.application.config_for(:configuration)["admin_statistics_url"] url = Rails.application.config_for(:configuration)["admin_statistics_url"]
return if url.blank? return if url.blank?
content_tag(:li) do content_tag(:li) do
sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root') sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root')
end end
end end
# 1 手机类型0 邮箱类型 # 1 手机类型0 邮箱类型
# 注意新版的login是自动名生成的 # 注意新版的login是自动名生成的
def phone_mail_type value def phone_mail_type value
value =~ /^1\d{10}$/ ? 1 : 0 value =~ /^1\d{10}$/ ? 1 : 0
end end
def strip(str) def strip(str)
str.to_s.strip.presence str.to_s.strip.presence
end end
end end

View File

@ -1,169 +1,218 @@
module RepositoriesHelper module RepositoriesHelper
def render_permission(user, project) def render_permission(user, project)
return "Admin" if user&.admin? return "Admin" if user&.admin?
project.get_premission(user) project.get_premission(user)
end end
def render_decode64_content(str) def render_decode64_content(str)
return nil if str.blank? return nil if str.blank?
Base64.decode64(str).force_encoding("UTF-8").encode("UTF-8", invalid: :replace) Base64.decode64(str).force_encoding("UTF-8").encode("UTF-8", invalid: :replace)
end end
def download_type(str) 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 = %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? default_type.include?(str&.downcase) || str.blank?
end end
def image_type?(str) def image_type?(str)
default_type = %w(png jpg gif tif psd svg bmp webp jpeg ico psd) default_type = %w(png jpg gif tif psd svg bmp webp jpeg ico psd)
default_type.include?(str&.downcase) default_type.include?(str&.downcase)
end end
def is_readme?(type, str) def is_readme?(type, str)
return false if type != 'file' || str.blank? return false if type != 'file' || str.blank?
readme_types = ["readme.md", "readme", "readme_en.md", "readme_zh.md", "readme_en", "readme_zh"] readme_types = ["readme.md", "readme", "readme_en.md", "readme_zh.md", "readme_en", "readme_zh"]
readme_types.include?(str.to_s.downcase) readme_types.include?(str.to_s.downcase) || str =~ CustomRegexp::MD_REGEX
end end
def render_commit_author(author_json) def render_commit_author(author_json)
return nil if author_json.blank? || (author_json["id"].blank? && author_json['name'].blank?) return nil if author_json.blank? || (author_json["id"].blank? && author_json['name'].blank?)
if author_json["id"].present? if author_json["id"].present?
return find_user_by_gitea_uid author_json['id'] return find_user_by_gitea_uid author_json['id']
end end
if author_json["id"].nil? && (author_json["name"].present? && author_json["email"].present?) 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"]) return find_user_by_login_and_mail(author_json['name'], author_json["email"])
end end
end end
def render_cache_commit_author(author_json) def render_cache_commit_author(author_json)
if author_json["name"].present? && author_json["email"].present? if author_json["name"].present? && author_json["email"].present?
return find_user_in_redis_cache(author_json['name'], author_json['email']) return find_user_in_redis_cache(author_json['name'], author_json['email'])
end end
if author_json["Name"].present? && author_json["Email"].present? if author_json["Name"].present? && author_json["Email"].present?
return find_user_in_redis_cache(author_json['Name'], author_json['Email']) return find_user_in_redis_cache(author_json['Name'], author_json['Email'])
end end
end end
def readme_render_decode64_content(str, owner, repo, ref, path) def readme_render_decode64_content(str, owner, repo, ref, path)
return nil if str.blank? return nil if str.blank?
begin begin
content = Base64.decode64(str).force_encoding('UTF-8') content = Base64.decode64(str).force_encoding('UTF-8')
c_regex = /\!\[.*?\]\((.*?)\)/ c_regex = /\!\[.*?\]\((.*?)\)/
src_regex = /src=\"(.*?)\"/ src_regex = /src=\"(.*?)\"/
src2_regex = /src='(.*?)'/ src2_regex = /src='(.*?)'/
ss = content.to_s.scan(c_regex) ss = content.to_s.scan(c_regex)
ss_src = content.scan(src_regex) ss_src = content.scan(src_regex)
ss_src2 = content.scan(src2_regex) ss_src2 = content.scan(src2_regex)
total_images = ss + ss_src + ss_src2 total_images = ss + ss_src + ss_src2
if total_images.length > 0 if total_images.length > 0
total_images.each do |s| total_images.each do |s|
begin begin
image_title = /\"(.*?)\"/ image_title = /\"(.*?)\"/
r_content = s[0] r_content = s[0]
remove_title = r_content.to_s.scan(image_title) remove_title = r_content.to_s.scan(image_title)
# if remove_title.length > 0 # if remove_title.length > 0
# r_content = r_content.gsub(/#{remove_title[0]}/, "").strip # r_content = r_content.gsub(/#{remove_title[0]}/, "").strip
# end # end
path_last = r_content path_last = r_content
path_current = "" path_current = ""
# 相对路径处理 # 相对路径处理
if r_content.start_with?("../") if r_content.start_with?("../")
relative_path_length = r_content.split("../").size - 1 relative_path_length = r_content.split("../").size - 1
path_pre = path.split("/").size - 1 - relative_path_length path_pre = path.split("/").size - 1 - relative_path_length
path_pre = 0 if path_pre < 0 path_pre = 0 if path_pre < 0
path_current = path_pre == 0 ? "" : path.split("/")[0..path_pre].join("/") path_current = path_pre == 0 ? "" : path.split("/")[0..path_pre].join("/")
path_last = r_content.split("../").last path_last = r_content.split("../").last
elsif r_content.start_with?("/") # 根路径处理 elsif r_content.start_with?("/") # 根路径处理
path_last = r_content[1..r_content.size] path_last = r_content[1..r_content.size]
else else
path_current = path path_current = path
end end
# if r_content.include?("?") # if r_content.include?("?")
# new_r_content = r_content + "&raw=true" # new_r_content = r_content + "&raw=true"
# else # else
# new_r_content = r_content + "?raw=true" # new_r_content = r_content + "?raw=true"
# end # end
new_r_content = r_content new_r_content = r_content
unless r_content.include?("http://") || r_content.include?("https://") || r_content.include?("mailto:") unless r_content.include?("http://") || r_content.include?("https://") || r_content.include?("mailto:")
# new_r_content = "#{path}" + new_r_content # 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 new_r_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{path_current}/#{path_last}&ref=#{ref}"].join
end end
content = content.gsub(/src=\"#{r_content}\"/, "src=\"#{new_r_content}\"").gsub(/src='#{r_content}'/, "src=\"#{new_r_content}\"") content = content.gsub(/src=\"#{r_content}\"/, "src=\"#{new_r_content}\"").gsub(/src='#{r_content}'/, "src=\"#{new_r_content}\"")
rescue rescue
next next
end end
end end
end end
return content return content
rescue rescue
return str return str
end end
end end
# unix_time values for example: 1604382982 # author hui.he
def render_format_time_with_unix(unix_time) def new_readme_render_decode64_content(str, owner, repo, ref, readme_path, readme_name)
Time.at(unix_time).strftime("%Y-%m-%d %H:%M") file_path = readme_path.include?('/') ? readme_path.gsub("/#{readme_name}", '') : readme_path.gsub("#{readme_name}", '')
end return nil if str.blank?
content = Base64.decode64(str).force_encoding('UTF-8')
# date for example: 2020-11-01T19:57:27+08:00 s_regex = /\[.*?\]\((.*?)\)/
def render_format_time_with_date(date) src_regex = /src=\"(.*?)\"/
date.to_time.strftime("%Y-%m-%d %H:%M") ss = content.to_s.scan(s_regex)
end ss_src = content.to_s.scan(src_regex)
total_sources = ss + ss_src
def decode64_content(entry, owner, repo, ref, path=nil) total_sources.uniq!
if is_readme?(entry['type'], entry['name']) total_sources.each do |s|
# content = Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content'] begin
content = entry['content'] s_content = s[0]
path = URI.escape(entry['path']).to_s.downcase.gsub("/readme.md","") # 链接直接跳过不做替换
readme_render_decode64_content(content, owner, repo, ref, path) next if s_content.starts_with?('http://') || s_content.starts_with?('https://') || s_content.starts_with?('mailto:') || s_content.blank?
else ext = File.extname(s_content)[1..-1]
file_type = File.extname(entry['name'].to_s)[1..-1]
if image_type?(file_type) if image_type?(ext) || download_type(ext)
return entry['content'].nil? ? Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content'] : entry['content'] s_content = File.expand_path(s_content, file_path)
end s_content = s_content.split("#{Rails.root}/")[1]
if download_type(file_type) # content = content.gsub(s[0], "/#{s_content}")
return entry['content'] s_content = [base_url, "/api/#{owner&.login}/#{repo.identifier}/raw?filepath=#{s_content}&ref=#{ref}"].join
end content = content.gsub(s[0], s_content)
render_decode64_content(entry['content']) else
end path = [owner&.login, repo&.identifier, 'tree', ref, file_path].join("/")
end s_content = File.expand_path(s_content, path)
s_content = s_content.split("#{Rails.root}/")[1]
def base64_to_image(path, content) content = content.gsub(s[0], "/#{s_content}")
# generate to https://git.trusite.net/pawm36ozq/-/raw/branch/master/entrn.png" end
content = Base64.decode64(content) rescue
File.open(path, 'wb') { |f| f.write(content) } next
end end
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("/") return content
file_name = full_path.split("/")[-1] rescue
# 用户名/项目标识/文件路径 return str
dir_path = generate_dir_path(full_path.split("/"+file_name)[0]) end
file_path = [dir_path, file_name].join('/') # unix_time values for example: 1604382982
def render_format_time_with_unix(unix_time)
puts "##### render_download_image_url file_path: #{file_path}" Time.at(unix_time).strftime("%Y-%m-%d %H:%M")
base64_to_image(file_path, content) end
file_path = file_path[6..-1]
File.join(base_url, file_path) # date for example: 2020-11-01T19:57:27+08:00
end def render_format_time_with_date(date)
date.to_time.strftime("%Y-%m-%d %H:%M")
def generate_dir_path(dir_path) end
# tmp_dir_path
# eg: jasder/forgeplus/raw/branch/ref def readme_decode64_content(entry, owner, repo, ref, path=nil)
dir_path = ["public", tmp_dir, dir_path].join('/') Rails.logger.info("entry===#{entry["type"]} #{entry["name"]}")
puts "#### dir_path: #{dir_path}" content = Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
unless Dir.exists?(dir_path) Rails.logger.info("content===#{content}")
FileUtils.mkdir_p(dir_path) ##不成功这里会抛异常 # readme_render_decode64_content(content, owner, repo, ref)
end new_readme_render_decode64_content(content, owner, repo, ref, entry['path'], entry['name'])
dir_path end
end
def decode64_content(entry, owner, repo, ref, path=nil)
def tmp_dir if is_readme?(entry['type'], entry['name'])
"repo" Rails.logger.info("entry===#{entry["type"]} #{entry["name"]}")
end content = Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
Rails.logger.info("content===#{content}")
end # 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

View File

@ -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

View File

@ -13,7 +13,7 @@ module Gitea
end end
def success? def success?
@error.nil? @error.nil? && @result[:status].to_s == "success"
end end
def result def result

View File

@ -4,6 +4,24 @@ class SendTemplateMessageJob < ApplicationJob
def perform(source, *args) def perform(source, *args)
Rails.logger.info "SendTemplateMessageJob [args] #{args}" Rails.logger.info "SendTemplateMessageJob [args] #{args}"
case source 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' when 'FollowTip'
watcher_id = args[0] watcher_id = args[0]
watcher = Watcher.find_by_id(watcher_id) watcher = Watcher.find_by_id(watcher_id)

View File

@ -1,7 +1,7 @@
module CustomRegexp module CustomRegexp
PHONE = /1\d{10}/ 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/ 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/ LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/
NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/ NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/
PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\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 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_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
MD_REGEX = /^.+(\.[m|M][d|D])$/
end end

View File

@ -16,8 +16,7 @@ class MessageTemplate < ApplicationRecord
PLATFORM = 'GitLink' PLATFORM = 'GitLink'
def self.build_init_data def self.build_init_data
MessageTemplate::IssueAssignerExpire.destroy_all MessageTemplate.where.not(type: 'MessageTemplate::CustomTip').destroy_all
MessageTemplate::IssueCreatorExpire.destroy_all
self.create(type: 'MessageTemplate::FollowedTip', sys_notice: '<b>{nickname}</b> 关注了你', notification_url: '{baseurl}/{login}') self.create(type: 'MessageTemplate::FollowedTip', sys_notice: '<b>{nickname}</b> 关注了你', notification_url: '{baseurl}/{login}')
email_html = File.read("#{email_template_html_dir}/issue_assigned.html") email_html = File.read("#{email_template_html_dir}/issue_assigned.html")
self.create(type: 'MessageTemplate::IssueAssigned', sys_notice: '{nickname1}在 <b>{nickname2}/{repository}</b> 指派给你一个疑修:<b>{title}</b>', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 指派给你一个疑修") self.create(type: 'MessageTemplate::IssueAssigned', sys_notice: '{nickname1}在 <b>{nickname2}/{repository}</b> 指派给你一个疑修:<b>{title}</b>', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 指派给你一个疑修")

View File

@ -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

View File

@ -141,6 +141,7 @@ class MessageTemplate::ProjectSettingChanged < MessageTemplate
navbar.gsub!('devops', '工作流') navbar.gsub!('devops', '工作流')
navbar.gsub!('versions', '里程碑') navbar.gsub!('versions', '里程碑')
navbar.gsub!('resources', '资源库') navbar.gsub!('resources', '资源库')
navbar.gsub!('services', '服务')
if change_count > 1 if change_count > 1
content.sub!('{ifnavbar}', '<br/>') content.sub!('{ifnavbar}', '<br/>')
else else
@ -290,6 +291,7 @@ class MessageTemplate::ProjectSettingChanged < MessageTemplate
navbar.gsub!('devops', '工作流') navbar.gsub!('devops', '工作流')
navbar.gsub!('versions', '里程碑') navbar.gsub!('versions', '里程碑')
navbar.gsub!('resources', '资源库') navbar.gsub!('resources', '资源库')
navbar.gsub!('services', '服务')
if change_count > 1 if change_count > 1
content.sub!('{ifnavbar}', '<br/>') content.sub!('{ifnavbar}', '<br/>')
else else

View File

@ -1,411 +1,420 @@
# == Schema Information # == Schema Information
# #
# Table name: projects # Table name: projects
# #
# id :integer not null, primary key # id :integer not null, primary key
# name :string(255) default(""), not null # name :string(255) default(""), not null
# description :text(4294967295) # description :text(4294967295)
# homepage :string(255) default("") # homepage :string(255) default("")
# is_public :boolean default("1"), not null # is_public :boolean default("1"), not null
# parent_id :integer # parent_id :integer
# created_on :datetime # created_on :datetime
# updated_on :datetime # updated_on :datetime
# identifier :string(255) # identifier :string(255)
# status :integer default("1"), not null # status :integer default("1"), not null
# lft :integer # lft :integer
# rgt :integer # rgt :integer
# inherit_members :boolean default("0"), not null # inherit_members :boolean default("0"), not null
# project_type :integer default("0") # project_type :integer default("0")
# hidden_repo :boolean default("0"), not null # hidden_repo :boolean default("0"), not null
# attachmenttype :integer default("1") # attachmenttype :integer default("1")
# user_id :integer # user_id :integer
# dts_test :integer default("0") # dts_test :integer default("0")
# enterprise_name :string(255) # enterprise_name :string(255)
# organization_id :integer # organization_id :integer
# project_new_type :integer # project_new_type :integer
# gpid :integer # gpid :integer
# forked_from_project_id :integer # forked_from_project_id :integer
# forked_count :integer default("0") # forked_count :integer default("0")
# publish_resource :integer default("0") # publish_resource :integer default("0")
# visits :integer default("0") # visits :integer default("0")
# hot :integer default("0") # hot :integer default("0")
# invite_code :string(255) # invite_code :string(255)
# qrcode :string(255) # qrcode :string(255)
# qrcode_expiretime :integer default("0") # qrcode_expiretime :integer default("0")
# script :text(65535) # script :text(65535)
# training_status :integer default("0") # training_status :integer default("0")
# rep_identifier :string(255) # rep_identifier :string(255)
# project_category_id :integer # project_category_id :integer
# project_language_id :integer # project_language_id :integer
# praises_count :integer default("0") # license_id :integer
# watchers_count :integer default("0") # ignore_id :integer
# issues_count :integer default("0") # praises_count :integer default("0")
# pull_requests_count :integer default("0") # watchers_count :integer default("0")
# language :string(255) # issues_count :integer default("0")
# versions_count :integer default("0") # pull_requests_count :integer default("0")
# issue_tags_count :integer default("0") # language :string(255)
# closed_issues_count :integer default("0") # versions_count :integer default("0")
# open_devops :boolean default("0") # issue_tags_count :integer default("0")
# gitea_webhook_id :integer # closed_issues_count :integer default("0")
# open_devops_count :integer default("0") # open_devops :boolean default("0")
# recommend :boolean default("0") # gitea_webhook_id :integer
# platform :integer default("0") # open_devops_count :integer default("0")
# license_id :integer # recommend :boolean default("0")
# ignore_id :integer # platform :integer default("0")
# default_branch :string(255) default("master") # default_branch :string(255) default("master")
# website :string(255) # website :string(255)
# lesson_url :string(255) # lesson_url :string(255)
# is_pinned :boolean default("0") # is_pinned :boolean default("0")
# recommend_index :integer default("0") # recommend_index :integer default("0")
# #
# Indexes # Indexes
# #
# index_projects_on_forked_from_project_id (forked_from_project_id) # index_projects_on_forked_from_project_id (forked_from_project_id)
# index_projects_on_identifier (identifier) # index_projects_on_identifier (identifier)
# index_projects_on_invite_code (invite_code) # index_projects_on_invite_code (invite_code)
# index_projects_on_is_public (is_public) # index_projects_on_is_public (is_public)
# index_projects_on_lft (lft) # index_projects_on_lft (lft)
# index_projects_on_license_id (license_id) # index_projects_on_license_id (license_id)
# index_projects_on_name (name) # index_projects_on_name (name)
# index_projects_on_platform (platform) # index_projects_on_platform (platform)
# index_projects_on_project_category_id (project_category_id) # index_projects_on_project_category_id (project_category_id)
# index_projects_on_project_language_id (project_language_id) # index_projects_on_project_language_id (project_language_id)
# index_projects_on_project_type (project_type) # index_projects_on_project_type (project_type)
# index_projects_on_recommend (recommend) # index_projects_on_recommend (recommend)
# index_projects_on_rgt (rgt) # index_projects_on_rgt (rgt)
# index_projects_on_status (status) # index_projects_on_status (status)
# index_projects_on_updated_on (updated_on) # index_projects_on_updated_on (updated_on)
# #
class Project < ApplicationRecord class Project < ApplicationRecord
include Matchable include Matchable
include Publicable include Publicable
include Watchable include Watchable
include ProjectOperable include ProjectOperable
include Dcodes include Dcodes
# common:开源托管项目 # common:开源托管项目
# mirror:普通镜像项目,没有定时同步功能 # mirror:普通镜像项目,没有定时同步功能
# sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作 # sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作
# #
enum project_type: { sync_mirror: 2, mirror: 1, common: 0 } enum project_type: { sync_mirror: 2, mirror: 1, common: 0 }
# forge: trustie平台项目 educoder: educoder平台项目 默认为forge平台 # forge: trustie平台项目 educoder: educoder平台项目 默认为forge平台
enum platform: { forge: 0, educoder: 1 } enum platform: { forge: 0, educoder: 1 }
belongs_to :ignore, optional: true belongs_to :ignore, optional: true
belongs_to :license, optional: true belongs_to :license, optional: true
belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, 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 :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_category, optional: true , :counter_cache => true
belongs_to :project_language, 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 belongs_to :forked_from_project, class_name: 'Project', optional: true, foreign_key: :forked_from_project_id
has_many :project_trends, dependent: :destroy has_many :project_trends, dependent: :destroy
has_many :watchers, as: :watchable, dependent: :destroy has_many :watchers, as: :watchable, dependent: :destroy
has_many :fork_users, 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_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_many :forked_projects, class_name: 'Project', foreign_key: :forked_from_project_id
has_one :project_educoder, dependent: :destroy has_one :project_educoder, dependent: :destroy
has_one :project_score, dependent: :destroy has_one :project_score, dependent: :destroy
has_one :repository, dependent: :destroy has_one :repository, dependent: :destroy
has_many :pull_requests, dependent: :destroy has_many :pull_requests, dependent: :destroy
has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy
has_many :issues, dependent: :destroy has_many :issues, dependent: :destroy
# has_many :user_grades, dependent: :destroy # has_many :user_grades, dependent: :destroy
has_many :attachments, as: :container, dependent: :destroy has_many :attachments, as: :container, dependent: :destroy
has_one :project_score, dependent: :destroy has_one :project_score, dependent: :destroy
has_many :versions, -> { order("versions.created_on DESC, versions.name DESC") }, 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_many :praise_treads, as: :praise_tread_object, dependent: :destroy
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
has_one :project_detail, dependent: :destroy has_one :project_detail, dependent: :destroy
has_many :project_units, dependent: :destroy has_many :project_units, dependent: :destroy
has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy
has_many :pinned_projects, dependent: :destroy has_many :pinned_projects, dependent: :destroy
has_many :has_pinned_users, through: :pinned_projects, source: :user 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 :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
after_create :incre_user_statistic, :incre_platform_statistic has_many :user_trace_tasks, dependent: :destroy
after_save :check_project_members after_create :incre_user_statistic, :incre_platform_statistic
before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data after_save :check_project_members
before_destroy :decre_project_common, :decre_forked_from_project_count before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data
after_destroy :decre_user_statistic, :decre_platform_statistic before_destroy :decre_project_common, :decre_forked_from_project_count
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)} after_destroy :decre_user_statistic, :decre_platform_statistic
scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)} 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 :recommend, -> { visible.project_statics_select.where(recommend: true) } scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)}
scope :pinned, -> {where(is_pinned: true)} 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 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 validate :validate_sensitive_string
org_public_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'common'})).to_sql
if user_id.present? def self.all_visible(user_id=nil)
org_limit_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'limited'})).to_sql user_projects_sql = Project.joins(:owner).where(users: {type: 'User'}).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 org_public_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'common'})).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 if user_id.present?
else org_limit_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'limited'})).to_sql
return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } ) AS projects").visible 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
end return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } UNION #{ org_limit_projects_sql } UNION #{org_privacy_projects_sql} ) AS projects").visible
end else
return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } ) AS projects").visible
def reset_cache_data end
CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id) end
if changes[:user_id].present?
CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first) def reset_cache_data
CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last) CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id)
end if changes[:user_id].present?
if changes[:project_language_id].present? CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first)
first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first) CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last)
last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last) end
CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id) if changes[:project_language_id].present?
CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id) first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first)
CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}) last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last)
CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}) CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id)
end CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id)
if changes[:is_public].present? CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1})
if changes[:is_public][0] && !changes[:is_public][1] CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1})
CacheAsyncClearJob.perform_later('project_rank_service', self.id) end
end if changes[:is_public].present?
if !changes[:is_public][0] && changes[:is_public][1] if changes[:is_public][0] && !changes[:is_public][1]
$redis_cache.srem("v2-project-rank-deleted", self.id) CacheAsyncClearJob.perform_later('project_rank_service', self.id)
end end
end if !changes[:is_public][0] && changes[:is_public][1]
end $redis_cache.srem("v2-project-rank-deleted", self.id)
end
def decre_project_common end
CacheAsyncClearJob.perform_later('project_common_service', self.id) end
end
def decre_project_common
def decre_forked_from_project_count CacheAsyncClearJob.perform_later('project_common_service', self.id)
forked_project = self.forked_from_project end
if forked_project.present?
forked_project.decrement(:forked_count, 1) def decre_forked_from_project_count
forked_project.save forked_project = self.forked_from_project
end if forked_project.present?
end forked_project.decrement(:forked_count, 1)
forked_project.save
def incre_user_statistic end
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
end
def incre_user_statistic
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)
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
end
def decre_user_statistic
def incre_platform_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)
CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}) end
end
def incre_platform_statistic
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})
CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}) end
end
def decre_platform_statistic
def is_full_public CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1})
owner = self.owner end
if owner.is_a?(Organization)
return self.is_public && owner&.visibility == "common" def is_full_public
else owner = self.owner
return self.is_public if owner.is_a?(Organization)
end return self.is_public && owner&.visibility == "common"
end else
return self.is_public
def reset_unmember_followed end
if changes[:is_public].present? && changes[:is_public] == [true, false] end
self.watchers.where.not(user_id: self.all_collaborators).destroy_all
end def reset_unmember_followed
end if changes[:is_public].present? && changes[:is_public] == [true, false]
self.watchers.where.not(user_id: self.all_collaborators).destroy_all
def set_invite_code end
if self.invite_code.nil? end
self.invite_code= self.generate_dcode('invite_code', 6)
end def set_invite_code
end if self.invite_code.nil?
self.invite_code= self.generate_dcode('invite_code', 6)
def set_recommend_and_is_pinned end
self.recommend = self.recommend_index.zero? ? false : true end
# 私有项目不允许设置精选和推荐
unless self.is_public def set_recommend_and_is_pinned
self.recommend = false self.recommend = self.recommend_index.zero? ? false : true
self.recommend_index = 0 # 私有项目不允许设置精选和推荐
self.is_pinned = false unless self.is_public
end self.recommend = false
end self.recommend_index = 0
self.is_pinned = false
def self.search_project(search) end
ransack(name_or_identifier_cont: search) end
end
# 创建者 def self.search_project(search)
def creator ransack(name_or_identifier_cont: search)
User.find(user_id).full_name end
end # 创建者
def creator
def members_user_infos User.find(user_id).full_name
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") end
# 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") def members_user_infos
end 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")
def to_param # .pluck("users.id", "users.login","users.lastname", "users.firstname")
self.identifier.parameterize end
end
def to_param
def get_issues_count(status_id) self.identifier.parameterize
if status_id.present? end
self&.issues.issue_issue.select(:id, :status_id).where(status_id: status_id)&.pluck(:id).size
else def get_issues_count(status_id)
self&.issues.issue_issue.select(:id)&.pluck(:id).size if status_id.present?
end self&.issues.issue_issue.select(:id, :status_id).where(status_id: status_id)&.pluck(:id).size
end else
self&.issues.issue_issue.select(:id)&.pluck(:id).size
def get_pull_requests_count(status_id) end
if status_id.present? end
self&.pull_requests.select(:id, :status).where(status: status_id)&.pluck(:id).size
else def get_pull_requests_count(status_id)
self&.pull_requests.select(:id)&.pluck(:id).size if status_id.present?
end self&.pull_requests.select(:id, :status).where(status: status_id)&.pluck(:id).size
end else
self&.pull_requests.select(:id)&.pluck(:id).size
#创建项目管理员 end
def check_project_members end
return if owner.is_a?(Organization)
unless members.present? && members.exists?(user_id: self.user_id) #创建项目管理员
member_params = { def check_project_members
user_id: self.user_id, return if owner.is_a?(Organization)
project_id: self.id unless members.present? && members.exists?(user_id: self.user_id)
} member_params = {
user_member = Member.new(member_params) user_id: self.user_id,
if user_member.save project_id: self.id
role_id = Role.select(:id,:position).where(position: 3)&.first&.id }
MemberRole.create!(member_id: user_member.id ,role_id: role_id) user_member = Member.new(member_params)
end if user_member.save
end role_id = Role.select(:id,:position).where(position: 3)&.first&.id
end MemberRole.create!(member_id: user_member.id ,role_id: role_id)
end
end
def self.init_bluck_repository end
Project.includes(:repository).find_each do |project|
puts project.id
next if project.owner.blank? def self.init_bluck_repository
if project.repository.blank? Project.includes(:repository).find_each do |project|
puts "########### start create repositoy #############" puts project.id
Repository.create!(project_id: project.id, identifier: Project.generate_identifier, user_id: project&.owner&.id) next if project.owner.blank?
end if project.repository.blank?
end puts "########### start create repositoy #############"
end Repository.create!(project_id: project.id, identifier: Project.generate_identifier, user_id: project&.owner&.id)
end
def self.generate_identifier end
str_arr = (("a".."z").to_a + ("A".."Z").to_a) end
str = str_arr.shuffle[0..8].join def self.generate_identifier
while Repository.exists?(identifier: str) str_arr = (("a".."z").to_a + ("A".."Z").to_a)
str = str_arr.shuffle[0..8].join
end str = str_arr.shuffle[0..8].join
str while Repository.exists?(identifier: str)
end str = str_arr.shuffle[0..8].join
end
def self.list_user_projects(user_id) str
projects = Project.is_private.select(:id,:user_id) end
user_not_show_1 = projects.where("user_id != ?",user_id).pluck(:id).uniq
def self.list_user_projects(user_id)
user_show_2 = projects.joins(:members).where("members.user_id = ?", user_id).pluck(:id).uniq projects = Project.is_private.select(:id,:user_id)
Project.where.not(id: (user_not_show_1 - user_show_2).uniq) user_not_show_1 = projects.where("user_id != ?",user_id).pluck(:id).uniq
end
user_show_2 = projects.joins(:members).where("members.user_id = ?", user_id).pluck(:id).uniq
def members_count Project.where.not(id: (user_not_show_1 - user_show_2).uniq)
members.select(:id).size end
end
def members_count
members.select(:id).size
def can_visited? end
is_public? || User.current.admin? || member?(User.current)
end
def can_visited?
def releases_size(current_user_id, type) is_public? || User.current.admin? || member?(User.current)
if current_user_id == self.user_id && type.to_s == "all" end
self.repository.version_releases_count
else def releases_size(current_user_id, type)
self.repository.version_releases.releases_size if current_user_id == self.user_id && type.to_s == "all"
end self.repository.version_releases_count
end else
self.repository.version_releases.releases_size
def contributor_users end
self.pull_requests.select(:user_id).pluck(:user_id).uniq.size end
end
def contributor_users
def open_issues_count self.pull_requests.select(:user_id).pluck(:user_id).uniq.size
issues_count - closed_issues_count end
end
def open_issues_count
def numerical_for_project_type issues_count - closed_issues_count
self.class.name.constantize.project_types["#{self.project_type}"] end
end
def numerical_for_project_type
def watched_by? user self.class.name.constantize.project_types["#{self.project_type}"]
watchers.pluck(:user_id).include? user&.id end
end
def watched_by? user
def praised_by? user watchers.pluck(:user_id).include? user&.id
praise_treads.pluck(:user_id).include? user&.id end
end
def praised_by? user
def get_premission user praise_treads.pluck(:user_id).include? user&.id
return "Owner" if owner?(user) end
return "Manager" if manager?(user)
return "Developer" if develper?(user) def get_premission user
return "Reporter" if reporter?(user) return "Owner" if owner?(user)
return "Manager" if manager?(user)
return "" return "Developer" if develper?(user)
end return "Reporter" if reporter?(user)
def fork_project return ""
Project.find_by(id: self.forked_from_project_id) end
end
def fork_project
def self.members_projects(member_user_id) Project.find_by(id: self.forked_from_project_id)
joins(:members).where(members: { user_id: member_user_id}) end
end
def self.members_projects(member_user_id)
def self.find_with_namespace(namespace_path, identifier) joins(:members).where(members: { user_id: member_user_id})
logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} " end
user = Owner.find_by_login namespace_path def self.find_with_namespace(namespace_path, identifier)
project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}") logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} "
return nil if project.blank?
user = Owner.find_by_login namespace_path
[project, user] user = Owner.new(login: namespace_path) if user.nil?
end project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
return nil if project.blank?
def ci_reactivate?
open_devops_count > 0 [project, user]
end end
def ci_reactivate!(ci_repo) def ci_reactivate?
ci_repo.update_column(:repo_active, 1) open_devops_count > 0
update_column(:open_devops, true) end
increment!(:open_devops_count)
end def ci_reactivate!(ci_repo)
ci_repo.update_column(:repo_active, 1)
def self.sync_educoder_shixun(url, private_token, page, per_page) update_column(:open_devops, true)
SyncEducoderShixunJob.perform_later(url, private_token, page, per_page) increment!(:open_devops_count)
end end
def self.update_common_projects_count! def self.sync_educoder_shixun(url, private_token, page, per_page)
ps = ProjectStatistic.first SyncEducoderShixunJob.perform_later(url, private_token, page, per_page)
ps.increment!(:common_projects_count) unless ps.blank? end
end
def self.update_common_projects_count!
def self.update_mirror_projects_count! ps = ProjectStatistic.first
ps = ProjectStatistic.first ps.increment!(:common_projects_count) unless ps.blank?
ps.increment!(:mirror_projects_count) unless ps.blank? end
end
def self.update_mirror_projects_count!
def set_updated_on(time) ps = ProjectStatistic.first
return if time.blank? ps.increment!(:mirror_projects_count) unless ps.blank?
update_column(:updated_on, time) end
end
def set_updated_on(time)
def is_transfering return if time.blank?
applied_transfer_project&.common? ? true : false update_column(:updated_on, time)
end end
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

View File

@ -16,7 +16,7 @@
class ProjectUnit < ApplicationRecord class ProjectUnit < ApplicationRecord
belongs_to :project 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} validates :unit_type, uniqueness: { scope: :project_id}

View File

@ -48,6 +48,10 @@ class Repository < ApplicationRecord
self.identifier.parameterize self.identifier.parameterize
end 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 # with repository is mirror
def set_mirror! def set_mirror!
self.build_mirror(status: Mirror.statuses[:waiting]).save self.build_mirror(status: Mirror.statuses[:waiting]).save

View File

@ -175,6 +175,7 @@ class User < Owner
has_many :system_notification_histories has_many :system_notification_histories
has_many :system_notifications, through: :system_notification_histories has_many :system_notifications, through: :system_notification_histories
has_one :trace_user, dependent: :destroy has_one :trace_user, dependent: :destroy
has_many :user_trace_tasks, dependent: :destroy
# Groups and active users # Groups and active users
scope :active, lambda { where(status: [STATUS_ACTIVE, STATUS_EDIT_INFO]) } scope :active, lambda { where(status: [STATUS_ACTIVE, STATUS_EDIT_INFO]) }

View File

@ -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

View File

@ -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

View File

@ -24,7 +24,8 @@ class Projects::TransferService < ApplicationService
private private
def update_owner 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) project.update!(user_id: new_owner.id)
end end

View File

@ -1,11 +1,11 @@
# 代码溯源 查询检测结果 # 代码溯源 查询检测结果
class Trace::CheckResultService < Trace::ClientService 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 @token = token
@project_name = project_name @project = project
@file_name = file_name @file_name = file_name
@page_num = page_num @page_num = page_num
@page_size = page_size @page_size = page_size
@ -19,7 +19,7 @@ class Trace::CheckResultService < Trace::ClientService
private private
def request_params def request_params
{ {
product_name: project_name, product_name: Digest::MD5.hexdigest(project&.id.to_s)[0...20],
file_name: file_name, file_name: file_name,
pageNum: page_num, pageNum: page_num,
pageSize: page_size, pageSize: page_size,

View File

@ -11,26 +11,25 @@ class Trace::CheckService < Trace::ClientService
end end
def call def call
result = authed_post(token, url, {data: request_params}) result = http_authed_post(token, url, {data: request_params})
reponse = render_response(result) reponse = render_response(result)
end end
private private
def request_params 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_name: Digest::MD5.hexdigest(project&.id.to_s)[0...20],
product_type: project&.category&.name, product_type: project&.project_category&.name || '其他',
code_type: project&.language&.name, code_type: project&.project_language&.name || '其他',
product_desc: project&.description, product_desc: project&.description,
git_url: repo['clone_url'], git_url: repo['clone_url'],
if_branch: if_branch, if_branch: if_branch,
branch_tag: branch_tag branch_tag: branch_tag
} }.compact
end end
def url def url
"/user/check".freeze "/user/check".freeze
end end
end end

View File

@ -12,6 +12,19 @@ class Trace::ClientService < ApplicationService
conn.post(full_url(url), params[:data]) conn.post(full_url(url), params[:data])
end 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={}) def get(url, params={})
puts "[trace][GET] request params: #{params}" puts "[trace][GET] request params: #{params}"
conn.get do |req| conn.get do |req|
@ -100,11 +113,22 @@ class Trace::ClientService < ApplicationService
end end
def render_response(response) def render_response(response)
status = response.status if response.is_a?(Faraday::Response)
body = JSON.parse(response&.body) 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
end end

View File

@ -1,4 +1,7 @@
# 代码溯源 导出pdf # 代码溯源 导出pdf
require 'open-uri'
require 'fileutils'
class Trace::PdfReportService < Trace::ClientService class Trace::PdfReportService < Trace::ClientService
attr_accessor :token, :task_id attr_accessor :token, :task_id
@ -9,15 +12,23 @@ class Trace::PdfReportService < Trace::ClientService
end end
def call def call
result = authed_get(token, url, request_params) content = open("#{domain}#{base_url}#{url}?task_id=#{task_id}", "Authorization" => token)
response = render_response(result) 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 end
private private
def request_params def check_file_path
{ FileUtils.mkdir_p save_path
task_id: task_id end
}
def save_path
"public/trace_task_results"
end end
def url def url

View File

@ -11,8 +11,8 @@
</thead> </thead>
<tbody> <tbody>
<% if message_templates.present? %> <% if message_templates.present? %>
<% message_templates.each_with_index do |message_template_type, index| %> <% message_templates.each_with_index do |message_template, index| %>
<% message_template = message_template_type.constantize.last%> <%# message_template = message_template_type.constantize.last%>
<tr class="project-language-item-<%= message_template.id %>"> <tr class="project-language-item-<%= message_template.id %>">
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td> <td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td><%= message_template.simple_type %></td> <td><%= message_template.simple_type %></td>

View File

@ -3,7 +3,13 @@
<% end %> <% end %>
<div id="admins-message-templates-content"> <div id="admins-message-templates-content">
<div class="box search-form-container project-list-form"> <div class="box search-form-container project-list-form">
<%= link_to "初始化数据", init_data_admins_message_templates_path, class: "btn btn-primary pull-right", "data-disabled-with":"...初始化数据" %> <%= form_tag(admins_message_templates_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
<%= text_field_tag(:search, params[:search], class: 'form-control col-12 col-md-2 mr-3', placeholder: '名称检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<input type="reset" class="btn btn-secondary clear-btn" value="清空"/>
<% end %>
<%= link_to "初始化数据", init_data_admins_message_templates_path, class: "btn btn-primary mr-3 pull-right", "data-disabled-with":"...初始化数据" %>
<%= link_to "新增", new_admins_message_template_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %>
</div> </div>
<div class="box admin-list-container message-templates-list-container"> <div class="box admin-list-container message-templates-list-container">
<%= render partial: 'admins/message_templates/list', locals: { message_templates: @message_templates } %> <%= render partial: 'admins/message_templates/list', locals: { message_templates: @message_templates } %>

View File

@ -0,0 +1,2 @@
$("#admins-message-templates-content").html("<%= j render partial: 'admins/message_templates/form', locals:{type: 'create'} %>")
createMDEditor('message-template-email-editor', { height: 500, placeholder: '请输入邮件模版' });

View File

@ -31,7 +31,9 @@
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %> <%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<% end %> <% end %>
<%= javascript_void_link '导入用户', class: 'btn btn-secondary btn-sm', data: { toggle: 'modal', target: '.admin-import-user-modal'} %> <%= link_to '下载导入模板', "/导入用户模板.xlsx", class: 'btn btn-secondary mr-3' %>
<%= javascript_void_link '导入用户', class: 'btn btn-secondary', data: { toggle: 'modal', target: '.admin-import-user-modal'} %>
</div> </div>

View File

@ -1,6 +1,6 @@
json.total_count @projects.total_count json.total_count @projects.total_count
json.projects @projects.each do |project| json.projects @projects.each do |project|
json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id) json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id, :is_public)
json.mirror_url project.repository&.mirror_url json.mirror_url project.repository&.mirror_url
json.type project.numerical_for_project_type json.type project.numerical_for_project_type
json.praised project.praised_by?(current_user) json.praised project.praised_by?(current_user)

View File

@ -9,7 +9,7 @@ json.num_projects team.num_projects
json.num_users team.num_users json.num_users team.num_users
json.units team.team_units.pluck(:unit_type) json.units team.team_units.pluck(:unit_type)
json.users team.team_users.each do |user| json.users team.team_users.each do |user|
json.partial! "organizations/user_detail", user: user&.user json.partial! "organizations/user_detail", user: user&.user if user&.user
end end
json.is_admin @is_admin json.is_admin @is_admin
json.is_member team.is_member?(current_user.id) json.is_member team.is_member?(current_user.id)

View File

@ -9,7 +9,10 @@ if @project.forge?
json.path entry['path'] json.path entry['path']
json.type entry['type'] json.type entry['type']
json.size entry['size'] json.size entry['size']
is_readme = is_readme?(entry['type'], entry['name'])
if is_readme
json.replace_content readme_decode64_content(entry, @owner, @repository, @ref, @path)
end
json.content (direct_download || image_type || is_dir) ? nil : decode64_content(entry, @owner, @repository, @ref, @path) json.content (direct_download || image_type || is_dir) ? nil : decode64_content(entry, @owner, @repository, @ref, @path)
json.target entry['target'] json.target entry['target']
@ -25,7 +28,7 @@ if @project.forge?
json.direct_download direct_download json.direct_download direct_download
json.image_type image_type json.image_type image_type
json.is_readme_file is_readme?(entry['type'], entry['name']) json.is_readme_file is_readme
json.commit do json.commit do
json.partial! 'last_commit', latest_commit: entry['latest_commit'] json.partial! 'last_commit', latest_commit: entry['latest_commit']
end end

View File

@ -0,0 +1,5 @@
if @user.present?
json.partial! 'users/user', locals: { user: @user }
else
json.null
end

View File

@ -1,27 +1,29 @@
json.username @user.full_name json.username @user.full_name
json.real_name @user.real_name json.real_name @user.real_name
json.nickname @user.nickname json.nickname @user.nickname
json.gender @user.gender json.gender @user.gender
json.login @user.login json.login @user.login
json.user_id @user.id json.user_id @user.id
json.image_url url_to_avatar(@user) json.image_url url_to_avatar(@user)
json.admin @user.admin? json.admin @user.admin?
json.is_teacher @user.user_extension&.teacher? json.is_teacher @user.user_extension&.teacher?
json.user_identity @user.identity json.user_identity @user.identity
json.tidding_count 0 json.tidding_count 0
json.user_phone_binded @user.phone.present? json.user_phone_binded @user.phone.present?
json.need_edit_info @user.need_edit_info? json.need_edit_info @user.need_edit_info?
# json.phone @user.phone # json.phone @user.phone
# json.email @user.mail # json.email @user.mail
json.profile_completed @user.profile_is_completed? json.profile_completed @user.profile_is_completed?
json.professional_certification @user.professional_certification json.professional_certification @user.professional_certification
json.devops_step @user.devops_step json.devops_step @user.devops_step
json.ci_certification @user.ci_certification? json.ci_certification @user.ci_certification?
json.email @user.mail json.email @user.mail
json.province @user.province json.province @user.province
json.city @user.city json.city @user.city
json.custom_department @user.custom_department json.custom_department @user.custom_department
json.description @user.description json.description @user.description
json.super_description @user.super_description json.super_description @user.super_description
json.(@user, :show_email, :show_department, :show_location, :show_super_description) json.(@user, :show_email, :show_department, :show_location, :show_super_description)
json.message_unread_total @message_unread_total json.message_unread_total @message_unread_total
json.has_trace_user @user.trace_user.present?
json.is_new @user.login.present? && params[:login].to_s.include?("#{@user.login}")

View File

@ -214,6 +214,8 @@ Rails.application.routes.draw do
post :remote_password post :remote_password
post :change_password post :change_password
post :check post :check
post :login_check
post :simple_update
end end
end end
@ -247,6 +249,7 @@ Rails.application.routes.draw do
get :trustie_projects get :trustie_projects
get :trustie_related_projects get :trustie_related_projects
post :sync_user_info post :sync_user_info
get :email_search
scope '/ci', module: :ci do scope '/ci', module: :ci do
scope do scope do
@ -427,6 +430,20 @@ Rails.application.routes.draw do
end end
end end
namespace :traces do
resources :trace_users, only: [:create]
scope "/:owner/:repo" do
resource :projects, path: '/', only: [:index] do
member do
post :tasks
get :task_results
get :reload_task
get :task_pdf
end
end
end
end
# Project Area START # Project Area START
scope "/:owner/:repo" do scope "/:owner/:repo" do
scope do scope do
@ -712,7 +729,7 @@ Rails.application.routes.draw do
get :history get :history
end end
end end
resources :message_templates, only: [:index, :edit, :update] do resources :message_templates, only: [:index, :new, :create, :edit, :update] do
collection do collection do
get :init_data get :init_data
end end

View File

@ -0,0 +1,12 @@
class CreateUserTraceTasks < ActiveRecord::Migration[5.2]
def change
create_table :user_trace_tasks do |t|
t.references :user
t.references :project
t.string :branch_tag
t.string :task_id
t.timestamps
end
end
end

View File

@ -0,0 +1,35 @@
namespace :sync_outer_repo do
desc "sync outer repository to gitlink"
task done: :environment do
file = ENV['file'] || "rcore-os_repo.xlsx"
user_type = ENV['type'] || "User"
doc = SimpleXlsxReader.open("#{Rails.root}/public/#{file}")
data = doc.sheets.first.rows
data.each_with_index do |row, index|
next if index == 0
begin
user = (user_type == "User" ? User.find_by(login: row[1]) : Owner.find_by(login: row[1]))
project = user.projects.find_by(identifier: row[4])
unless project.present?
p_category = ProjectCategory.find_or_create_by(name: row[6])
p_language = ProjectLanguage.find_or_create_by(name: row[7].to_s.split("/")[0]) if row[7]
p_license = License.find_by(name: row[8])
mirror_params = {
user_id: user.id,
name: row[5],
description: row[9],
repository_name: row[4],
project_category_id: p_category.id,
project_language_id: p_language&.id,
clone_addr: row[10]
}
Projects::MigrateService.call(user, mirror_params)
end
puts "sync outer repository to gitlink Success repo: #{row[5]} username: #{row[0]}"
rescue Exception => e
puts "sync outer repository to gitlink Error repo: #{row[5]} username: #{row[0]}, error:#{e}"
end
end
end
end

View File

@ -467,6 +467,9 @@
<li> <li>
<a href="#4f8579f6bd" class="toc-h2 toc-link" data-title="取消迁移项目">取消迁移项目</a> <a href="#4f8579f6bd" class="toc-h2 toc-link" data-title="取消迁移项目">取消迁移项目</a>
</li> </li>
<li>
<a href="#88c0b2e25f" class="toc-h2 toc-link" data-title="退出项目">退出项目</a>
</li>
</ul> </ul>
</li> </li>
<li> <li>
@ -543,6 +546,26 @@
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a href="#traces" class="toc-h1 toc-link" data-title="Traces">Traces</a>
<ul class="toc-list-h2">
<li>
<a href="#ca438fc3ca" class="toc-h2 toc-link" data-title="代码溯源初始化">代码溯源初始化</a>
</li>
<li>
<a href="#bb16c601f1" class="toc-h2 toc-link" data-title="代码分析结果列表">代码分析结果列表</a>
</li>
<li>
<a href="#32497859e0" class="toc-h2 toc-link" data-title="新建分析">新建分析</a>
</li>
<li>
<a href="#7b3a48e274" class="toc-h2 toc-link" data-title="重新扫描">重新扫描</a>
</li>
<li>
<a href="#87775b1430" class="toc-h2 toc-link" data-title="下载报告">下载报告</a>
</li>
</ul>
</li>
<li> <li>
<a href="#pulls" class="toc-h1 toc-link" data-title="Pulls">Pulls</a> <a href="#pulls" class="toc-h1 toc-link" data-title="Pulls">Pulls</a>
<ul class="toc-list-h2"> <ul class="toc-list-h2">
@ -4968,7 +4991,7 @@ http://localhost:3000/api/yystopf/ceshi/menu_list | jq
<tr> <tr>
<td>menu_name</td> <td>menu_name</td>
<td>string</td> <td>string</td>
<td>导航名称, home:主页,code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,activity:动态,setting:仓库设置</td> <td>导航名称, home:主页,code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,wiki:维基,services:服务,activity:动态,setting:仓库设置</td>
</tr> </tr>
</tbody></table> </tbody></table>
@ -5131,7 +5154,7 @@ http://localhost:3000/api/yystopf/ceshi/project_units.json
<td></td> <td></td>
<td></td> <td></td>
<td>array</td> <td>array</td>
<td>项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑</td> <td>项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,wiki:维基,resources:资源库,services:服务</td>
</tr> </tr>
</tbody></table> </tbody></table>
<h3 id='7447e4874e-2'>返回字段说明:</h3> <h3 id='7447e4874e-2'>返回字段说明:</h3>
@ -6026,6 +6049,49 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
</span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2021-04-26 09:54"</span><span class="p">,</span><span class="w"> </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2021-04-26 09:54"</span><span class="p">,</span><span class="w">
</span><span class="nl">"time_ago"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1分钟前"</span><span class="w"> </span><span class="nl">"time_ago"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1分钟前"</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div><h2 id='88c0b2e25f'>退出项目</h2>
<p>供项目成员(开发者、报告者)退出项目用</p>
<blockquote>
<p>示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json
</code></pre></div><div class="highlight"><pre class="highlight javascript tab-javascript"><code><span class="k">await</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">'</span><span class="s1">POST /api/:owner/:repo/quit.json</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div><h3 id='http-15'>HTTP 请求</h3>
<p><code>POST /api/:owner/:repo/quit.json</code></p>
<h3 id='1f9ac54b15-13'>请求参数</h3>
<table><thead>
<tr>
<th>参数</th>
<th>必选</th>
<th>默认</th>
<th>类型</th>
<th>字段说明</th>
</tr>
</thead><tbody>
<tr>
<td>owner</td>
<td></td>
<td></td>
<td>string</td>
<td>用户登录名</td>
</tr>
<tr>
<td>repo</td>
<td></td>
<td></td>
<td>string</td>
<td>项目标识identifier</td>
</tr>
</tbody></table>
<blockquote>
<p>返回的JSON示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div><h1 id='repositories'>Repositories</h1><h2 id='0a366f0c25'>仓库详情</h2> </span></code></pre></div><h1 id='repositories'>Repositories</h1><h2 id='0a366f0c25'>仓库详情</h2>
<p>仓库详情</p> <p>仓库详情</p>
@ -9302,6 +9368,289 @@ http://localhost:3000/api/yystopf/ceshi/webhooks/3/test.json
<aside class="success"> <aside class="success">
Success Data. Success Data.
</aside> </aside>
<h1 id='traces'>Traces</h1><h2 id='ca438fc3ca'>代码溯源初始化</h2>
<p>用户同意协议后请求的接口,创建代码溯源的账号</p>
<blockquote>
<p>示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> POST <span class="se">\</span>
http://localhost:3000/api/traces/trace_users.json
</code></pre></div><div class="highlight"><pre class="highlight javascript tab-javascript"><code><span class="k">await</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">'</span><span class="s1">POST /api/traces/trace_users.json</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div><h3 id='http'>HTTP 请求</h3>
<p><code>POST api/traces/trace_users.json</code></p>
<blockquote>
<p>返回的JSON示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
<h2 id='bb16c601f1'>代码分析结果列表</h2>
<p>查询项目下代码分析的结果</p>
<blockquote>
<p>示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> GET <span class="se">\</span>
http://localhost:3000/api/traces/yystopf/many_branch/task_results.json
</code></pre></div><div class="highlight"><pre class="highlight javascript tab-javascript"><code><span class="k">await</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">'</span><span class="s1">GET /api/traces/:owner/:repo/task_results.json</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div><h3 id='http-2'>HTTP 请求</h3>
<p><code>GET api/traces/:owner/:repo/task_results.json</code></p>
<h3 id='1f9ac54b15'>请求参数</h3>
<table><thead>
<tr>
<th>参数</th>
<th>必选</th>
<th>默认</th>
<th>类型</th>
<th>字段说明</th>
</tr>
</thead><tbody>
<tr>
<td>owner</td>
<td></td>
<td></td>
<td>string</td>
<td>项目所有者标识</td>
</tr>
<tr>
<td>repo</td>
<td></td>
<td></td>
<td>string</td>
<td>项目标识</td>
</tr>
<tr>
<td>page</td>
<td></td>
<td>1</td>
<td>int</td>
<td>页码</td>
</tr>
<tr>
<td>limit</td>
<td></td>
<td>15</td>
<td>int</td>
<td>每页数量</td>
</tr>
</tbody></table>
<h3 id='90889036d2'>返回字段说明(暂缺)</h3>
<!-- 参数 | 类型 | 字段说明
--------- | ----------- | -----------
total_count |int |总数 |
public_keys.id |int |ID|
public_keys.name |string|密钥标题|
public_keys.content |string|密钥内容|
public_keys.fingerprint |string|密钥标识|
public_keys.created_time |string|密钥创建时间| -->
<blockquote>
<p>返回的JSON示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"accuracy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"20"</span><span class="p">,</span><span class="w">
</span><span class="nl">"code_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"C"</span><span class="p">,</span><span class="w">
</span><span class="nl">"depth"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
</span><span class="nl">"detect_flag"</span><span class="p">:</span><span class="w"> </span><span class="s2">"快速-组件级"</span><span class="p">,</span><span class="w">
</span><span class="nl">"detect_rule"</span><span class="p">:</span><span class="w"> </span><span class="s2">"快速-组件级,2,20,,开源软件,50,10"</span><span class="p">,</span><span class="w">
</span><span class="nl">"detect_startdate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-05-10 15:59:46 "</span><span class="p">,</span><span class="w">
</span><span class="nl">"detect_status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"fail"</span><span class="p">,</span><span class="w">
</span><span class="nl">"detectflag"</span><span class="p">:</span><span class="w"> </span><span class="s2">"快速-组件级"</span><span class="p">,</span><span class="w">
</span><span class="nl">"fail_reason"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Invalid package type"</span><span class="p">,</span><span class="w">
</span><span class="nl">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"many_branch.zip"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license_process"</span><span class="p">:</span><span class="w"> </span><span class="s2">"100"</span><span class="p">,</span><span class="w">
</span><span class="nl">"licenseparam"</span><span class="p">:</span><span class="w"> </span><span class="s2">"开源软件"</span><span class="p">,</span><span class="w">
</span><span class="nl">"package_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"product_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"84727546110"</span><span class="p">,</span><span class="w">
</span><span class="nl">"project_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"6dbc3e42-5857-4ca4-a54d-58fd9dbf6dc5"</span><span class="p">,</span><span class="w">
</span><span class="nl">"sim_process"</span><span class="p">:</span><span class="w"> </span><span class="s2">"100"</span><span class="p">,</span><span class="w">
</span><span class="nl">"similarity_process"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
</span><span class="nl">"task_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"15139171-091b-4316-98b1-6068970efa44"</span><span class="p">,</span><span class="w">
</span><span class="nl">"totalsize"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"uid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"78"</span><span class="p">,</span><span class="w">
</span><span class="nl">"vuln_process"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"vulnlevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
<h2 id='32497859e0'>新建分析</h2>
<p>用户选择仓库分支进行代码分析的接口</p>
<blockquote>
<p>示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> POST <span class="se">\</span>
http://localhost:3000/api/traces/yystopf/many_branch/tasks.json
</code></pre></div><div class="highlight"><pre class="highlight javascript tab-javascript"><code><span class="k">await</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">'</span><span class="s1">POST /api/traces/:owner/:repo/tasks.json</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div><h3 id='http-3'>HTTP 请求</h3>
<p><code>POST api/traces/:owner/:repo/tasks.json</code></p>
<h3 id='1f9ac54b15-2'>请求参数</h3>
<table><thead>
<tr>
<th>参数</th>
<th>必选</th>
<th>默认</th>
<th>类型</th>
<th>字段说明</th>
</tr>
</thead><tbody>
<tr>
<td>owner</td>
<td></td>
<td></td>
<td>string</td>
<td>项目所有者标识</td>
</tr>
<tr>
<td>repo</td>
<td></td>
<td></td>
<td>string</td>
<td>项目标识</td>
</tr>
<tr>
<td>branch_name</td>
<td></td>
<td></td>
<td>string</td>
<td>分支名称</td>
</tr>
</tbody></table>
<blockquote>
<p>返回的JSON示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
<h2 id='7b3a48e274'>重新扫描</h2>
<p>对代码分析结果进行再次分析</p>
<blockquote>
<p>示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> GET <span class="se">\</span>
http://localhost:3000/api/traces/yystopf/many_branch/reload_task.json
</code></pre></div><div class="highlight"><pre class="highlight javascript tab-javascript"><code><span class="k">await</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">'</span><span class="s1">GET /api/traces/:owner/:repo/reload_task.json</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div><h3 id='http-4'>HTTP 请求</h3>
<p><code>GET api/traces/:owner/:repo/reload_task.json</code></p>
<h3 id='1f9ac54b15-3'>请求参数</h3>
<table><thead>
<tr>
<th>参数</th>
<th>必选</th>
<th>默认</th>
<th>类型</th>
<th>字段说明</th>
</tr>
</thead><tbody>
<tr>
<td>owner</td>
<td></td>
<td></td>
<td>string</td>
<td>项目所有者标识</td>
</tr>
<tr>
<td>repo</td>
<td></td>
<td></td>
<td>string</td>
<td>项目标识</td>
</tr>
<tr>
<td>project_id</td>
<td></td>
<td></td>
<td>string</td>
<td>代码分析结果里的project_id</td>
</tr>
</tbody></table>
<blockquote>
<p>返回的JSON示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
<h2 id='87775b1430'>下载报告</h2>
<p>把代码分析的结果下载到本地</p>
<blockquote>
<p>示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> GET <span class="se">\</span>
http://localhost:3000/api/traces/yystopf/many_branch/task_pdf.json
</code></pre></div><div class="highlight"><pre class="highlight javascript tab-javascript"><code><span class="k">await</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">'</span><span class="s1">GET /api/traces/:owner/:repo/task_pdf.json</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div><h3 id='http-5'>HTTP 请求</h3>
<p><code>GET api/traces/:owner/:repo/task_pdf.json</code></p>
<h3 id='1f9ac54b15-4'>请求参数</h3>
<table><thead>
<tr>
<th>参数</th>
<th>必选</th>
<th>默认</th>
<th>类型</th>
<th>字段说明</th>
</tr>
</thead><tbody>
<tr>
<td>owner</td>
<td></td>
<td></td>
<td>string</td>
<td>项目所有者标识</td>
</tr>
<tr>
<td>repo</td>
<td></td>
<td></td>
<td>string</td>
<td>项目标识</td>
</tr>
<tr>
<td>task_id</td>
<td></td>
<td></td>
<td>string</td>
<td>代码分析结果里的task_id</td>
</tr>
</tbody></table>
<blockquote>
<p>返回的JSON示例:</p>
</blockquote>
<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"success"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<aside class="success">
Success — a happy kitten is an authenticated kitten!
</aside>
<h1 id='pulls'>Pulls</h1><h2 id='get-a-pull-request'>Get a pull request</h2> <h1 id='pulls'>Pulls</h1><h2 id='get-a-pull-request'>Get a pull request</h2>
<p>获取合并请求详情接口</p> <p>获取合并请求详情接口</p>

BIN
public/rcore-os_repo.xlsx Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe UserTraceTask, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end