Merge pull request '20220608版本' (#307) from develop into master

This commit is contained in:
xxq250 2022-06-08 14:44:09 +08:00
commit 4a886b4bb1
62 changed files with 2816 additions and 1530 deletions

3
.gitignore vendored
View File

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

View File

@ -1,6 +1,25 @@
class AccountsController < ApplicationController
before_action :require_login, only: [:login_check, :simple_update]
include ApplicationHelper
#skip_before_action :check_account, :only => [:logout]
def simple_update
simple_update_params.merge!(username: params[:username]&.gsub(/\s+/, ""))
simple_update_params.merge!(email: params[:email]&.gsub(/\s+/, ""))
simple_update_params.merge!(platform: (params[:platform] || 'forge')&.gsub(/\s+/, ""))
Register::RemoteForm.new(simple_update_params).validate!
ActiveRecord::Base.transaction do
result = auto_update(current_user, simple_update_params)
if result[:message].blank?
render_ok
else
render_error(result[:message])
end
end
end
def index
render json: session
end
@ -315,6 +334,11 @@ class AccountsController < ApplicationController
Register::CheckColumnsForm.new(check_params).validate!
render_ok
end
def login_check
Register::LoginCheckColumnsForm.new(check_params.merge(user: current_user)).validate!
render_ok
end
private
@ -383,4 +407,7 @@ class AccountsController < ApplicationController
params.permit(:username, :email, :password, :platform)
end
def simple_update_params
params.permit(:username, :email, :password, :platform)
end
end

View File

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

View File

@ -2,8 +2,24 @@ class Admins::MessageTemplatesController < Admins::BaseController
before_action :get_template, only: [:edit, :update, :destroy]
def index
message_templates = MessageTemplate.group(:type).count.keys
@message_templates = kaminari_array_paginate(message_templates)
message_templates = MessageTemplate.ransack(sys_notice_or_email_or_email_title_cont: params[:search]).result
@message_templates = kaminari_paginate(message_templates)
end
def new
@message_template = MessageTemplate::CustomTip.new
end
def create
@message_template = MessageTemplate::CustomTip.new(ignore_params)
if @message_template.save!
redirect_to admins_message_templates_path
flash[:success] = "创建消息模板成功"
else
render :new
flash[:danger] = "创建消息模板失败"
end
end
def edit

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -160,9 +160,9 @@ module Ci::CloudAccountManageable
state = SecureRandom.hex(8)
# redirect_uri eg:
# https://localhost:3000/login/oauth/authorize?client_id=94976481-ad0e-4ed4-9247-7eef106007a2&redirect_uri=http%3A%2F%2F121.69.81.11%3A80%2Flogin&response_type=code&state=9cab990b9cfb1805
redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login")
clientId = client_id(oauth)
grant_url = "#{Gitea.gitea_config[:domain]}/login/oauth/authorize?client_id=#{clientId}&redirect_uri=#{redirect_uri}&response_type=code&state=#{state}"
# redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login")
# clientId = client_id(oauth)
grant_url = "#{@cloud_account.drone_url}/login"
logger.info "[gitea] grant_url: #{grant_url}"
conn = Faraday.new(url: grant_url) do |req|
@ -179,7 +179,7 @@ module Ci::CloudAccountManageable
def drone_oauth_user!(url, state)
logger.info "[drone] drone_oauth_user url: #{url}"
conn = Faraday.new(url: "#{Gitea.gitea_config[:domain]}#{url}") do |req|
conn = Faraday.new(url: url) do |req|
req.request :url_encoded
req.adapter Faraday.default_adapter
req.headers["cookie"] = "_session_=#{SecureRandom.hex(28)}; _oauth_state_=#{state}"
@ -188,8 +188,8 @@ module Ci::CloudAccountManageable
response = conn.get
logger.info "[drone] response headers: #{response.headers}"
true
# response.headers['location'].include?('error') ? false : true
# true
response.headers['location'].include?('error') ? false : true
end
private

View File

@ -58,4 +58,32 @@ module RegisterHelper
end
end
def auto_update(user, params={})
return if params.blank?
result = {message: nil, user: nil}
before_login = user.login
user.login = params[:username]
user.password = params[:password]
user.mail = params[:email]
if user.save!
sync_params = {
password: params[:password].to_s,
email: params[:email],
login_name: params[:username],
new_name: params[:username],
source_id: 0
}
interactor = Gitea::User::UpdateInteractor.call(before_login, sync_params)
if interactor.success?
result[:user] = user
else
result[:message] = '用户同步Gitea失败!'
end
else
result[:message] = user.errors.full_messages.join(",")
return
end
end
end

View File

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

View File

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

View File

@ -12,7 +12,7 @@ class OwnersController < ApplicationController
def show
@owner = Owner.find_by(login: params[:id]) || Owner.find_by(id: params[:id])
return render_ok(type: 'User') unless @owner.present?
return render_not_found unless @owner.present?
# 组织
if @owner.is_a?(Organization)
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition

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("versions")) if @project.has_menu_permission("versions")
menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") && @project.forge?
menu.append(menu_hash_by_name("services")) if @project.has_menu_permission("services") && @project.forge? && (current_user.admin? || @project.member?(current_user.id))
menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") && @project.forge?
menu.append(menu_hash_by_name("activity"))
menu.append(menu_hash_by_name("settings")) if user_is_admin && @project.forge?

View File

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

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

View File

@ -16,6 +16,7 @@ includes:
- users
- projects
- repositories
- traces
- pulls
- issues
- 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示例:
@ -408,7 +408,7 @@ await octokit.request('POST /api/yystopf/ceshi/project_units')
### 请求参数
参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ----------
|unit_types |是| |array | 项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑 |
|unit_types |是| |array | 项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,wiki:维基,resources:资源库,services:服务 |
### 返回字段说明:
@ -849,4 +849,36 @@ await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.j
"created_at": "2021-04-26 09:54",
"time_ago": "1分钟前"
}
```
## 退出项目
供项目成员(开发者、报告者)退出项目用
> 示例:
```shell
curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json
```
```javascript
await octokit.request('POST /api/:owner/:repo/quit.json')
```
### HTTP 请求
`POST /api/:owner/:repo/quit.json`
### 请求参数
参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ----------
|owner |是| |string |用户登录名 |
|repo |是| |string |项目标识identifier |
> 返回的JSON示例:
```json
{
"status": 0,
"message": "success"
}
```

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,
:full_name, :location, :login_name, :max_repo_creation, :must_change_password, :password, :prohibit_login,
:source_id, :website
:source_id, :website, :new_name
validates :username, presence: true
validates :email, presence: true, format: { with: EMAIL_REGEX, multiline: true }

View File

@ -3,28 +3,40 @@ module Register
include ActiveModel::Model
private
def check_login(login)
def check_login(login, user=nil)
login = strip(login)
raise LoginError, "登录名格式有误" unless login =~ CustomRegexp::LOGIN
login_exist = Owner.exists?(login: login) || ReversedKeyword.check_exists?(login)
raise LoginError, '登录名已被使用' if login_exist
if user.present?
raise LoginError, '登录名已被使用' if login_exist && login != user&.login
else
raise LoginError, '登录名已被使用' if login_exist
end
end
def check_mail(mail)
def check_mail(mail, user=nil)
mail = strip(mail)
raise EmailError, "邮件格式有误" unless mail =~ CustomRegexp::EMAIL
mail_exist = Owner.exists?(mail: mail)
raise EmailError, '邮箱已被使用' if mail_exist
if user.present?
raise EmailError, '邮箱已被使用' if mail_exist && mail != user&.mail
else
raise EmailError, '邮箱已被使用' if mail_exist
end
end
def check_phone(phone)
def check_phone(phone, user=nil)
phone = strip(phone)
raise PhoneError, "手机号格式有误" unless phone =~ CustomRegexp::PHONE
phone_exist = Owner.exists?(phone: phone)
raise PhoneError, '手机号已被使用' if phone_exist
if user.present?
raise PhoneError, '手机号已被使用' if phone_exist && phone != user&.phone
else
raise PhoneError, '手机号已被使用' if phone_exist
end
end
end
end

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

View File

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

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
def success?
@error.nil?
@error.nil? && @result[:status].to_s == "success"
end
def result

View File

@ -4,6 +4,24 @@ class SendTemplateMessageJob < ApplicationJob
def perform(source, *args)
Rails.logger.info "SendTemplateMessageJob [args] #{args}"
case source
when 'CustomTip'
receivers_id, template_id, props = args[0], args[1], args[2]
template = MessageTemplate.find_by_id(template_id)
return unless template.present?
receivers = User.where(id: receivers_id).or(User.where(mail: receivers_id))
not_exists_receivers = receivers_id - receivers.pluck(:id) - receivers.pluck(:mail)
receivers_string, content, notification_url = MessageTemplate::CustomTip.get_message_content(receivers, template, props)
Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {receivers_id: receivers_id, template_id: template_id, props: props})
receivers.find_each do |receiver|
receivers_email_string, email_title, email_content = MessageTemplate::CustomTip.get_email_message_content(receiver, template, props)
Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content)
end
not_exists_receivers.each do |mail|
valid_email_regex = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i
next unless (mail =~ valid_email_regex)
email_title, email_content = MessageTemplate::CustomTip.get_email_content(template, props)
Notice::Write::EmailCreateService.call(mail, email_title, email_content)
end
when 'FollowTip'
watcher_id = args[0]
watcher = Watcher.find_by_id(watcher_id)

View File

@ -1,7 +1,7 @@
module CustomRegexp
PHONE = /1\d{10}/
EMAIL = /\A[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+\z/
LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
LOGIN = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]{4,15}\z/ #只含有数字、字母、下划线不能以下划线开头和结尾
LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/
NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/
PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/
@ -11,5 +11,5 @@ module CustomRegexp
URL_REGEX = /\A(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?\z/i
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
MD_REGEX = /^.+(\.[m|M][d|D])$/
end

View File

@ -16,8 +16,7 @@ class MessageTemplate < ApplicationRecord
PLATFORM = 'GitLink'
def self.build_init_data
MessageTemplate::IssueAssignerExpire.destroy_all
MessageTemplate::IssueCreatorExpire.destroy_all
MessageTemplate.where.not(type: 'MessageTemplate::CustomTip').destroy_all
self.create(type: 'MessageTemplate::FollowedTip', sys_notice: '<b>{nickname}</b> 关注了你', notification_url: '{baseurl}/{login}')
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} 指派给你一个疑修")

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

View File

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

View File

@ -16,7 +16,7 @@
class ProjectUnit < ApplicationRecord
belongs_to :project
enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7}
enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7, services: 8}
validates :unit_type, uniqueness: { scope: :project_id}

View File

@ -48,6 +48,10 @@ class Repository < ApplicationRecord
self.identifier.parameterize
end
def url
self['url'].blank? ? "#{Rails.application.config_for(:configuration)['platform_url']}/#{self.owner&.login}/#{self.identifier}.git" : self['url']
end
# with repository is mirror
def set_mirror!
self.build_mirror(status: Mirror.statuses[:waiting]).save

View File

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

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
def update_owner
project.members.map{|m| m.destroy! if m.user_id == owner.id || (new_owner.is_a?(Organization) && new_owner.is_member?(m.user_id)) }
project.members.map{|m| m.destroy! if m.user_id == owner.id || project.member(new_owner.id) || (new_owner.is_a?(Organization) && new_owner.is_member?(m.user_id)) }
project.set_owner_permission(new_owner)
project.update!(user_id: new_owner.id)
end

View File

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

View File

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

View File

@ -12,6 +12,19 @@ class Trace::ClientService < ApplicationService
conn.post(full_url(url), params[:data])
end
def http_authed_post(token, url, params={})
puts "[trace][POST] request params: #{params}"
puts "[trace][POST] request token: #{token}"
url = URI("#{full_url(url)}")
http = Net::HTTP.new(url.host, url.port)
http.read_timeout = 1200
request = Net::HTTP::Post.new(url)
request["Authorization"] = token
form_data = params[:data].stringify_keys.to_a
request.set_form form_data, 'multipart/form-data'
http.request(request)
end
def get(url, params={})
puts "[trace][GET] request params: #{params}"
conn.get do |req|
@ -100,11 +113,22 @@ class Trace::ClientService < ApplicationService
end
def render_response(response)
status = response.status
body = JSON.parse(response&.body)
if response.is_a?(Faraday::Response)
status = response.status
body = JSON.parse(response&.body)
log_error(status, body)
log_error(status, body)
return [body["code"], body["data"], body["error"]]
return [body["code"], body["data"], body["error"]]
end
if response.is_a?(Net::HTTPOK)
status = 200
body = JSON.parse(response&.body)
log_error(status, body)
return [body["code"], body["data"], body["error"]]
end
end
end

View File

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

View File

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

View File

@ -3,7 +3,13 @@
<% end %>
<div id="admins-message-templates-content">
<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 class="box admin-list-container message-templates-list-container">
<%= 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': '搜索中...') %>
<% 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>

View File

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

View File

@ -9,7 +9,7 @@ json.num_projects team.num_projects
json.num_users team.num_users
json.units team.team_units.pluck(:unit_type)
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
json.is_admin @is_admin
json.is_member team.is_member?(current_user.id)

View File

@ -9,7 +9,10 @@ if @project.forge?
json.path entry['path']
json.type entry['type']
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.target entry['target']
@ -25,7 +28,7 @@ if @project.forge?
json.direct_download direct_download
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.partial! 'last_commit', latest_commit: entry['latest_commit']
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.real_name @user.real_name
json.nickname @user.nickname
json.gender @user.gender
json.login @user.login
json.user_id @user.id
json.image_url url_to_avatar(@user)
json.admin @user.admin?
json.is_teacher @user.user_extension&.teacher?
json.user_identity @user.identity
json.tidding_count 0
json.user_phone_binded @user.phone.present?
json.need_edit_info @user.need_edit_info?
# json.phone @user.phone
# json.email @user.mail
json.profile_completed @user.profile_is_completed?
json.professional_certification @user.professional_certification
json.devops_step @user.devops_step
json.ci_certification @user.ci_certification?
json.email @user.mail
json.province @user.province
json.city @user.city
json.custom_department @user.custom_department
json.description @user.description
json.super_description @user.super_description
json.(@user, :show_email, :show_department, :show_location, :show_super_description)
json.message_unread_total @message_unread_total
json.username @user.full_name
json.real_name @user.real_name
json.nickname @user.nickname
json.gender @user.gender
json.login @user.login
json.user_id @user.id
json.image_url url_to_avatar(@user)
json.admin @user.admin?
json.is_teacher @user.user_extension&.teacher?
json.user_identity @user.identity
json.tidding_count 0
json.user_phone_binded @user.phone.present?
json.need_edit_info @user.need_edit_info?
# json.phone @user.phone
# json.email @user.mail
json.profile_completed @user.profile_is_completed?
json.professional_certification @user.professional_certification
json.devops_step @user.devops_step
json.ci_certification @user.ci_certification?
json.email @user.mail
json.province @user.province
json.city @user.city
json.custom_department @user.custom_department
json.description @user.description
json.super_description @user.super_description
json.(@user, :show_email, :show_department, :show_location, :show_super_description)
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 :change_password
post :check
post :login_check
post :simple_update
end
end
@ -247,6 +249,7 @@ Rails.application.routes.draw do
get :trustie_projects
get :trustie_related_projects
post :sync_user_info
get :email_search
scope '/ci', module: :ci do
scope do
@ -427,6 +430,20 @@ Rails.application.routes.draw do
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
scope "/:owner/:repo" do
scope do
@ -712,7 +729,7 @@ Rails.application.routes.draw do
get :history
end
end
resources :message_templates, only: [:index, :edit, :update] do
resources :message_templates, only: [:index, :new, :create, :edit, :update] do
collection do
get :init_data
end

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>
<a href="#4f8579f6bd" class="toc-h2 toc-link" data-title="取消迁移项目">取消迁移项目</a>
</li>
<li>
<a href="#88c0b2e25f" class="toc-h2 toc-link" data-title="退出项目">退出项目</a>
</li>
</ul>
</li>
<li>
@ -543,6 +546,26 @@
</li>
</ul>
</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>
<a href="#pulls" class="toc-h1 toc-link" data-title="Pulls">Pulls</a>
<ul class="toc-list-h2">
@ -4968,7 +4991,7 @@ http://localhost:3000/api/yystopf/ceshi/menu_list | jq
<tr>
<td>menu_name</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>
</tbody></table>
@ -5131,7 +5154,7 @@ http://localhost:3000/api/yystopf/ceshi/project_units.json
<td></td>
<td></td>
<td>array</td>
<td>项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑</td>
<td>项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,wiki:维基,resources:资源库,services:服务</td>
</tr>
</tbody></table>
<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">"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></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>
<p>仓库详情</p>
@ -9302,6 +9368,289 @@ http://localhost:3000/api/yystopf/ceshi/webhooks/3/test.json
<aside class="success">
Success Data.
</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>
<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