Merge branch 'standalone_develop' into dev_local_v2

This commit is contained in:
yystopf 2025-11-10 15:42:08 +08:00
commit a15f72272d
104 changed files with 1133 additions and 290134 deletions

View File

@ -134,11 +134,11 @@ $(document).on('turbolinks:load', function() {
rules: {
"user[password]": {
required: false,
minlength: 5
minlength: 8
},
"user[password_confirmation]": {
required: false,
minlength: 5,
minlength: 8,
equalTo: "#user_password"
},
},

View File

@ -292,6 +292,7 @@ class AccountsController < ApplicationController
UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id), :ip => request.remote_ip)
user.update_column(:last_login_on, Time.now)
session[:"#{default_yun_session}"] = user.id
UserOnline.login(user.id)
Rails.logger.info("#########_____session_default_yun_session__________###############{default_yun_session}")
# 注册完成后有一天的试用申请(先去掉)
# UserDayCertification.create(user_id: user.id, status: 1)

View File

@ -67,9 +67,9 @@ class Admins::DashboardsController < Admins::BaseController
CommitLog.count
end
@subject_name = ["用户数", "项目数", "组织数", "Issue数", "Issue评论数", "PR数", "Commit数"]
@subject_icon = ["fa-user","fa-git", "fa-sitemap", "fa-warning", "fa-comments", "fa-share-alt", "fa-upload"]
@subject_data = [@user_count, @project_count, @organization_count, @issue_count, @comment_count, @pr_count, @commit_count]
@subject_name = ["用户数", "项目数", "组织数", "Issue数", "Issue评论数", "PR数", "Commit数", "在线用户数"]
@subject_icon = ["fa-user","fa-git", "fa-sitemap", "fa-warning", "fa-comments", "fa-share-alt", "fa-upload", "fa-user"]
@subject_data = [@user_count, @project_count, @organization_count, @issue_count, @comment_count, @pr_count, @commit_count, UserOnline.count]
if EduSetting.get("open_baidu_tongji").to_s == "true"
tongji_service = Baidu::TongjiService.new

View File

@ -23,7 +23,7 @@ class Admins::LaboratorySettingsController < Admins::BaseController
def form_params
params.permit(:identifier, :name,
:nav_logo, :login_logo, :tab_logo, :oj_banner,
:nav_logo, :login_logo, :tab_logo, :pm_logo, :oj_banner,
:subject_banner, :course_banner, :competition_banner, :moop_cases_banner,
:footer, navbar: %i[name link hidden index])
end

View File

@ -21,6 +21,7 @@ class Api::Pm::DashboardsController < Api::Pm::BaseController
@all_issues = Issue.where(pm_project_id: pm_project_ids, pm_issue_type: pm_issue_types).order(created_on: :desc)
@issues = @all_issues.joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'})
@issues = @issues.where.not(status_id: 5) if params[:category] == "opened"
@issues = @issues.where("issues.id > 0")
@issues = kaminari_paginate(@issues.distinct.pm_includes)
@my_assign_requirements_count = @all_issues.where(pm_issue_type: 1).joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'}).size
@my_assign_tasks_count = @all_issues.where(pm_issue_type: 2).joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'}).size

View File

@ -19,22 +19,23 @@ class Api::Pm::PipelinesController < Api::Pm::BaseController
file = file_info.workflow_id
project = Project.find_by(gpid: file_info.repo_id)
next if project.blank?
unless db_files.include?(".gitea/workflows/#{file}")
pipeline = Action::Pipeline.find_or_initialize_by(pipeline_name: file.to_s.gsub(".yml", "").gsub(".yaml", ""),
file_name: ".gitea/workflows/#{file}",
branch: project.default_branch,
is_graphic_design: false,
disable: false,
project_id: project.id)
interactor = Repositories::EntriesInteractor.call(@owner, project.identifier, ".gitea/workflows/#{file}", ref: project.default_branch)
if interactor.success?
pipeline.yaml = decode64_content(interactor.result, @owner, project.repository, project.default_branch, nil)
end
pipeline.user_id = current_user.id
pipeline.save
# 导入的流水线统一先禁用
$gitea_hat_client.post_repos_actions_disable(project&.owner&.login, project&.identifier, {query: {workflow: file}}) rescue nil
end
# unless db_files.include?(".gitea/workflows/#{file}")
# pipeline = Action::Pipeline.find_or_initialize_by(pipeline_name: file.to_s.gsub(".yml", "").gsub(".yaml", ""),
# file_name: ".gitea/workflows/#{file}",
# branch: project.default_branch,
# disable: false,
# project_id: project.id)
# interactor = Repositories::EntriesInteractor.call(@owner, project.identifier, ".gitea/workflows/#{file}", ref: project.default_branch)
# if interactor.success?
# pipeline.yaml = decode64_content(interactor.result, @owner, project.repository, project.default_branch, nil)
# file = interactor.result
# pipeline.sha = file['content']['sha']
# end
# pipeline.user_id = current_user.id
# pipeline.save
# # 导入的流水线统一先禁用
# $gitea_hat_client.post_repos_actions_disable(project&.owner&.login, project&.identifier, {query: {workflow: file}}) rescue nil
# end
last_action_run = action_runs.where(repo_id: project.gpid).where(workflow_id: file).order(updated: :desc).first
last_action_run_json = last_action_run.present? ? {
id: last_action_run.id,

View File

@ -5,16 +5,18 @@ class Api::Pm::WeeklyIssuesController < Api::Pm::BaseController
return render_error('请输入正确的用户ID.') if params[:user_id].blank?
@all_issues = Issue.joins(:issue_participants).where(issue_participants: {participant_id: params[:user_id], participant_type: ['assigned']})
@all_issues = @all_issues.where(enterprise_identifier: @enterprise_identifier, pm_issue_type: [1,2,3])
@this_week_all_issues = @all_issues.where("due_date >= ? and start_date <= ?", Date.today.beginning_of_week.to_s, Date.today.end_of_week.to_s).distinct
@this_week_all_issues = @this_week_all_issues.order('created_on desc')
@next_week_all_issues = @all_issues.where("due_date >= ? and start_date <=?", (Date.today.beginning_of_week+1.week).to_s, (Date.today.end_of_week+1.week).to_s).distinct
@next_week_all_issues = @next_week_all_issues.order('created_on desc')
@weekly_begin_date = params[:weekly_begin_date].to_time || Date.today.beginning_of_week rescue Date.today.beginning_of_week
@weekly_end_date = params[:weekly_end_date].to_time || Date.today.end_of_week rescue Date.today.end_of_week
@this_week_all_issues = @all_issues.where("due_date >= ? and start_date <= ?", @weekly_begin_date.to_s, @weekly_end_date.to_s).distinct
@this_week_all_issues = @this_week_all_issues.order('created_on desc').pm_includes
@next_week_all_issues = @all_issues.where("due_date >= ? and start_date <=?", (@weekly_begin_date+1.week).to_s, (@weekly_end_date+1.week).to_s).distinct
@next_week_all_issues = @next_week_all_issues.order('created_on desc').pm_includes
@this_week_requirement_issues = @this_week_all_issues.where(pm_issue_type: 1)
@this_week_task_issues = @this_week_all_issues.where(pm_issue_type: 2)
@this_week_bug_issues = @this_week_all_issues.where(pm_issue_type: 3)
@close_requirement_issues = @this_week_requirement_issues.where(status_id: [3,5])
@close_task_issues = @this_week_task_issues.where(status_id: [3,5])
@close_bug_issues = @this_week_bug_issues.where(status_id: [3,5])
@close_requirement_issues = @this_week_requirement_issues.where(status_id: [3,5]).pm_includes
@close_task_issues = @this_week_task_issues.where(status_id: [3,5]).pm_includes
@close_bug_issues = @this_week_bug_issues.where(status_id: [3,5]).pm_includes
this_week_page = params[:this_week_page] || 1
this_week_limit = params[:this_week_limit] || 15
@this_week_all_issues = @this_week_all_issues.page(this_week_page).per(this_week_limit)
@ -27,7 +29,9 @@ class Api::Pm::WeeklyIssuesController < Api::Pm::BaseController
@enterprise_identifier = params[:enterprise_identifier] || ''
@all_issues = Issue.where(enterprise_identifier: @enterprise_identifier, pm_issue_type: [1,2,3])
@all_issues = @all_issues.where(pm_project_id: params[:pm_project_ids].split(",")) if params[:pm_project_ids].present?
@this_week_all_issues = @all_issues.where("due_date >= ? and start_date <= ?", Date.today.beginning_of_week.to_s, Date.today.end_of_week.to_s)
@weekly_begin_date = params[:weekly_begin_date] || Date.today.beginning_of_week.to_s
@weekly_end_date = params[:weekly_end_date] || Date.today.end_of_week.to_s
@this_week_all_issues = @all_issues.where("due_date >= ? and start_date <= ?", @weekly_begin_date, @weekly_end_date)
@this_week_all_issues_group_count = @this_week_all_issues.group(:pm_project_id).count
data = {}
@this_week_all_issues_group_count.keys.each do |pm_project_id|
@ -69,7 +73,7 @@ class Api::Pm::WeeklyIssuesController < Api::Pm::BaseController
@all_issues = @all_issues.where(pm_project_id: params[:pm_project_ids].split(",")) if params[:pm_project_ids].present?
@all_issues = @all_issues.where(pm_issue_type: params[:pm_issue_type].to_i) if params[:pm_issue_type].present?
@this_week_all_issues = @all_issues.where("due_date >= ? and start_date <= ?", @weekly_begin_date, @weekly_end_date)
@this_week_all_issues = @this_week_all_issues.order('created_on desc')
@this_week_all_issues = @this_week_all_issues.order('created_on desc').pm_includes
@this_week_all_issues = kaminari_paginate(@this_week_all_issues)
end
def personal_issues
@ -86,9 +90,9 @@ class Api::Pm::WeeklyIssuesController < Api::Pm::BaseController
end
@all_issues = @all_issues.where(enterprise_identifier: @enterprise_identifier, pm_issue_type: [1,2,3])
@this_week_all_issues = @all_issues.where("due_date >= ? and start_date <= ?", @weekly_begin_date, @weekly_end_date).distinct
@this_week_all_issues = @this_week_all_issues.order('created_on desc')
@this_week_all_issues = @this_week_all_issues.order('created_on desc').pm_includes
@next_week_all_issues = @all_issues.where("due_date >= ? and start_date <=?", (@weekly_begin_date.to_date+1.week).to_s, (@weekly_end_date.to_date+1.week).to_s).distinct
@next_week_all_issues = @next_week_all_issues.order('created_on desc')
@next_week_all_issues = @next_week_all_issues.order('created_on desc').pm_includes
this_week_page = params[:this_week_page] || 1
this_week_limit = params[:this_week_limit] || 15
@this_week_all_issues = @this_week_all_issues.page(this_week_page).per(this_week_limit)

View File

@ -37,6 +37,11 @@ class Api::V1::BaseController < ApplicationController
params.fetch(:page, 1)
end
def load_organization
@organization = Organization.find_by(login: params[:owner]) || Organization.find_by(id: params[:owner])
return render_not_found("组织不存在") if @organization.nil?
end
# 具有对仓库的管理权限
def require_manager_above
@project = load_project

View File

@ -0,0 +1,46 @@
class Api::V1::Organizations::ProjectsController < Api::V1::BaseController
before_action :load_organization
def index
public_projects_sql = @organization.projects.where(is_public: true).to_sql
private_projects_sql = @organization.projects
.where(is_public: false)
.joins(team_projects: {team: :team_users})
.where(team_users: {user_id: current_user.id}).to_sql
@projects = Project.from("( #{ public_projects_sql} UNION #{ private_projects_sql } ) AS projects")
# 表情处理
keywords = params[:search].to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
@projects = @projects.where(is_public: params[:is_public]) if params[:is_public].present?
@projects = @projects.where(id: params[:pm_project_repository_ids].split(',')) if params[:pm_project_repository_ids].present?
@projects = @projects.where.not(id: params[:exclude_ids].to_s.split(",")) if params[:exclude_ids].present?
@projects = @projects.where(project_type: ['mirror', 'common']).where("gpid is not null") if params[:actived].present?
@projects = @projects.ransack(name_or_identifier_cont: keywords).result if params[:search].present?
@projects = @projects.includes(:owner).order("projects.#{sort} #{sort_direction}")
@projects = kaminari_paginate(@projects)
end
private
def load_organization
@organization = Organization.find_by(login: params[:owner]) || Organization.find_by(id: params[:owner])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def org_limited_condition
@organization.organization_extension.limited? && !current_user.logged?
end
def org_privacy_condition
return false if current_user.admin?
@organization.organization_extension.privacy? && @organization.organization_users.where(user_id: current_user.id).blank?
end
def sort
Project.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'updated_on'
end
def sort_direction
%w(desc asc).include?(params[:sort_direction]) ? params[:sort_direction] : 'desc'
end
end

View File

@ -0,0 +1,42 @@
class Api::V1::Organizations::SyncRepositoriesController < Api::V1::BaseController
before_action :load_organization, only: [:create]
def create
return render_error("请输入正确的外部组织login") unless params[:external_login].present?
# 拉取外部组织仓库列表
url = URI("https://api.github.com/orgs/#{params[:external_login]}/repos?page=#{params[:page]}&per_page=#{params[:limit]}")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = "Bearer #{params[:external_token]}"
response = https.request(request)
if response.code == "200"
repos = JSON.parse(response.body)
repos.each do |repo|
# 检查是否已经存在该仓库
@project = @organization.projects.find_by(identifier: repo["name"])
if @project.present?
if @project.repository.mirror.failed?
@project.destroy!
mirror_params = {name: repo["name"], user_id: @organization.id, description: repo["description"], repository_name: repo["name"], auth_token: params[:external_token], clone_addr: repo["clone_url"]}
@project = ::Projects::MigrateService.call(current_user, mirror_params)
end
@project.update_column(:created_on, repo['created_at'].to_time)
@project.update_column(:updated_on, repo['updated_at'].to_time)
next
else
mirror_params = {name: repo["name"], user_id: @organization.id, description: repo["description"], repository_name: repo["name"], auth_token: params[:external_token], clone_addr: repo["clone_url"]}
@project = ::Projects::MigrateService.call(current_user, mirror_params)
@project.update_column(:created_on, repo['created_at'].to_time)
@project.update_column(:updated_on, repo['updated_at'].to_time)
end
end
end
end
private
def sync_repository_params
params.permit(:external_token)
end
end

View File

@ -58,4 +58,15 @@ class Api::V1::Projects::Actions::RunsController < Api::V1::Projects::Actions::B
redirect_to file_path
end
def cancel
return render_error("请输入正确的流水线记录ID") if params[:run_id].blank?
http = Gitea::Api::Hat::Http.new($gitea_hat_client.config)
gitea_result = http.post("/repos/#{@project&.owner&.login}/#{@project&.identifier}/actions/runs/#{params[:run_id]}/cancel") rescue nil
if gitea_result
render_ok
else
render_error("停止流水线任务失败")
end
end
end

View File

@ -1,5 +1,6 @@
class Api::V1::Projects::PipelinesController < Api::V1::BaseController
include RepositoriesHelper
include Api::ActionValidatorHelper
before_action :require_operate_above, except: [:upload_results, :run_results]
def index
@ -10,22 +11,22 @@ class Api::V1::Projects::PipelinesController < Api::V1::BaseController
db_files = pipelines.pluck(:file_name)
@run_result = []
@files.map { |i| i['name'] }.each do |file|
unless db_files.include?(".gitea/workflows/#{file}")
pipeline = Action::Pipeline.find_or_initialize_by(pipeline_name: file.to_s.gsub(".yml", "").gsub(".yaml", ""),
file_name: ".gitea/workflows/#{file}",
branch: @project.default_branch,
is_graphic_design: false,
disable: false,
project_id: @project.id)
interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, ".gitea/workflows/#{file}", ref: @project.default_branch)
if interactor.success?
pipeline.yaml = decode64_content(interactor.result, @owner, @project.repository, @project.default_branch, nil)
end
pipeline.user_id = current_user.id
pipeline.save
# 导入的流水线统一先禁用
$gitea_hat_client.post_repos_actions_disable(@project&.owner&.login, @project&.identifier, {query: {workflow: file}}) rescue nil
end
# unless db_files.include?(".gitea/workflows/#{file}")
# pipeline = Action::Pipeline.find_or_initialize_by(pipeline_name: file.to_s.gsub(".yml", "").gsub(".yaml", ""),
# file_name: ".gitea/workflows/#{file}",
# branch: @project.default_branch,
# is_graphic_design: false,
# disable: false,
# project_id: @project.id)
# interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, ".gitea/workflows/#{file}", ref: @project.default_branch)
# if interactor.success?
# pipeline.yaml = decode64_content(interactor.result, @owner, @project.repository, @project.default_branch, nil)
# end
# pipeline.user_id = current_user.id
# pipeline.save
# # 导入的流水线统一先禁用
# $gitea_hat_client.post_repos_actions_disable(@project&.owner&.login, @project&.identifier, {query: {workflow: file}}) rescue nil
# end
last_action_run = @action_runs.where(workflow_id: file).order(updated: :desc).first
last_action_run_json = last_action_run.present? ? {
id: last_action_run.id,
@ -79,12 +80,20 @@ class Api::V1::Projects::PipelinesController < Api::V1::BaseController
tip_exception("已经存在#{params[:pipeline_name]}流水线!") if has_pipeline.present?
if @pipeline.yaml.present?
sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
if sha.present?
interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update", params[:pipeline_name]).merge(sha: sha, from_path: "#{@pipeline.pipeline_name}"))
new_file_sha = get_pipeline_file_sha(".gitea/workflows/#{params[:pipeline_name]}.yml", @pipeline.branch)
# 重命名的新文件存在时直接修改新文件new_file_sha, 同时删除老文件
if sha.present? && new_file_sha.present?
interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update", params[:pipeline_name]).merge(sha: new_file_sha, from_path: ".gitea/workflows/#{params[:pipeline_name]}.yml"))
tip_exception(interactor.error) unless interactor.success?
del_interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, del_content_params(sha).merge(identifier: @project.identifier))
tip_exception(del_interactor.error) unless del_interactor.success?
elsif sha.present? && new_file_sha.blank?
interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update", params[:pipeline_name]).merge(sha: sha, from_path: ".gitea/workflows/#{@pipeline.pipeline_name}.yml"))
tip_exception(interactor.error) unless interactor.success?
end
end
end
@pipeline.user_id = current_user.id
@pipeline.pipeline_name = params[:pipeline_name]
@pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
@pipeline.branch = params[:branch] || @project.default_branch
@ -101,6 +110,10 @@ class Api::V1::Projects::PipelinesController < Api::V1::BaseController
@pipeline.pipeline_name = params[:pipeline_name] if params[:pipeline_name].present?
pipeline_yaml = params[:pipeline_yaml].present? ? params[:pipeline_yaml] : build_pipeline_yaml_new(@pipeline.pipeline_name, params[:pipeline_json])
tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
result = validate_string(pipeline_yaml)
unless result[:valid]
tip_exception(result[:errors].join(","))
end
@pipeline.yaml = pipeline_yaml
#Rails.logger.info "pipeline_yaml base64=========================#{Base64.encode64(@pipeline.yaml).gsub(/\n/, '')}"
sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
@ -108,7 +121,9 @@ class Api::V1::Projects::PipelinesController < Api::V1::BaseController
interactor = sha.present? ? Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update").merge(sha: sha)) : Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create"))
tip_exception(interactor.error) unless interactor.success?
file = interactor.result
@pipeline.user_id = current_user.id if @pipeline.user_id.blank?
@pipeline.pipeline_type = @pipeline.json.present? ? 2 : 1
@pipeline.sha = sha.present? ? sha : file['content']['sha']
@pipeline.save
render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: @pipeline.pipeline_name, file_name: @pipeline.file_name, sha: sha.present? ? sha : file['content']['sha'] })
end

View File

@ -6,13 +6,32 @@ class Api::V1::Projects::Pulls::PullsController < Api::V1::BaseController
@pulls = kaminari_paginate(@pulls)
end
before_action :load_pull_request, only: [:show, :files]
before_action :load_pull_request, only: [:show, :reopen, :files]
def show
@result_object = Api::V1::Projects::Pulls::GetService.call(@project, @pull_request, current_user&.gitea_token)
@last_review = @pull_request.reviews.order(created_at: :desc).take
end
def reopen
can_merge = @project&.pull_requests.where(head: @pull_request.head, base: @pull_request.base, status: 0, is_original: @pull_request.is_original, fork_project_id: @pull_request.fork_project_id)
if can_merge.present?
render json: {
status: -1,
redirect_url:"/#{@project.owner.login}/#{@project.identifier}/pulls/#{can_merge.first.gitea_number}",
message: "在这些分支之间的合并请求已存在",
}
else
@result_object = Api::V1::Projects::Pulls::ReopenService.call(@project, @pull_request, current_user)
if @result_object
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "reopened")
render_ok
else
render_error("合并请求重新打开失败!")
end
end
end
def files
if params[:filepath].present?
@result_object = $gitea_hat_client.get_repos_pulls_files_by_owner_repo_index_filepath(@project&.owner.login, @project&.identifier, @pull_request.gitea_number, CGI.escape(params[:filepath]), {query: {token: current_user&.gitea_token}})

View File

@ -0,0 +1,10 @@
class Api::V1::UserTokensController < Api::V1::BaseController
include AesCryptHelper
before_action :require_login
def show
password = decrypt(params[:password]) rescue params[:password].to_s
tip_exception("密码不正确") unless current_user.check_password?(password)
render_ok({access_token: current_user.gitea_token})
end
end

View File

@ -4,7 +4,7 @@ class Api::V1::Users::FeedbacksController < Api::V1::BaseController
before_action :check_auth_for_observe_user
def create
@result = Api::V1::Users::Feedbacks::CreateService.call(@observe_user, feedback_params)
@result = Api::V1::Users::Feedbacks::CreateService.call(@observe_user, feedback_params, request.remote_ip)
return render_error("反馈意见创建失败.") if @result.nil?
return render_ok
end

View File

@ -326,24 +326,14 @@ class ApplicationController < ActionController::Base
end
end
end
elsif params[:access_token].present?
user = User.find_by(gitea_token: params[:access_token])
tip_exception(401, "无效token") if user.blank?
User.current = user
else
User.current = find_current_user
uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
# # 开放课程通过链接访问的用户
# if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank?
# content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}"
#
# if Digest::MD5.hexdigest(content) == params[:chinaoocKey]
# user = open_class_user
# if user
# start_user_session(user)
# set_autologin_cookie(user)
# end
# User.current = user
# end
# end
if !User.current.logged? && Rails.env.development?
user = User.find 1
User.current = user
@ -366,6 +356,7 @@ class ApplicationController < ActionController::Base
end
end
end
UserOnline.login(User.current.id)
# User.current = User.find 81403
end

View File

@ -33,6 +33,7 @@ class AttachmentsController < ApplicationController
Rails.logger.info("request.host===#{request.host},request.referer===#{request.referer}")
tip_exception(403, "你没有权限访问") if request.host.present? && !request.referer.to_s.include?(request.host.to_s.gsub("www.",""))
normal_status(-1, "参数缺失") if params[:download_url].blank?
tip_exception('不可访问') if params[:download_url].to_s.include?("chinese_dictionary.txt")
url = base_url.starts_with?("https:") ? params[:download_url].to_s.gsub("http:", "https:") : params[:download_url].to_s
md5_file = Digest::MD5.hexdigest(params[:download_url])
FileUtils.mkdir_p("#{Rails.root}#{EduSetting.get("attachment_folder")}gitea/") unless Dir.exists?("#{Rails.root}#{EduSetting.get("attachment_folder")}gitea/")

View File

@ -0,0 +1,165 @@
require 'yaml'
require 'json'
module Api::ActionValidatorHelper
extend ActiveSupport::Concern
def validate_string(yaml_string)
begin
yaml_content = YAML.safe_load(yaml_string)
validate_content(yaml_content)
rescue Psych::SyntaxError => e
{ valid: false, errors: ["YAML语法错误: #{e.message}"] }
rescue => e
{ valid: false, errors: ["数据读取错误: #{e.message}"] }
end
end
def validate_content(data)
errors = []
warnings = []
# 基本结构验证
errors.concat(validate_basic_structure(data))
# 触发器验证
# errors.concat(validate_trigger(data))
# 任务验证
errors.concat(validate_job(data))
# 步骤验证
if data['jobs']
data['jobs'].each do |job_name, job_config|
errors.concat(validate_job_steps(job_name, job_config))
end
end
{
valid: errors.empty?,
errors: errors,
warnings: warnings,
job_count: data['jobs']&.size || 0
}
end
private
def validate_basic_structure(data)
errors = []
unless data.is_a?(Hash)
errors << "Action文件必须是YAML对象"
return errors
end
unless data.key?('name')
errors << "缺少必需字段: name"
end
unless data.key?(true)
errors << "缺少触发器配置: on"
end
unless data.key?('jobs')
errors << "缺少任务配置: jobs"
end
errors
end
def validate_trigger(data)
errors = []
if data[true]
if data[true]['push']
unless data[true]['push'].key?('branches')
errors << "push触发器缺少branches配置"
end
unless data[true]['push'].key?('paths-ignore')
errors << "push触发器缺少paths-ignore配置"
end
end
end
errors
end
def validate_job(data)
errors = []
if data['jobs']
jobs = data['jobs']
unless jobs.is_a?(Hash)
errors << "jobs字段必须是对象"
return errors
end
jobs.each do |job_name, job_config|
unless job_config.is_a?(Hash)
errors << "任务 #{job_name} 配置必须是对象"
next
end
# unless job_config.key?('name')
# errors << "任务 #{job_name} 缺少name字段"
# end
unless job_config.key?('runs-on')
errors << "任务 #{job_name} 缺少runs-on字段"
end
unless job_config.key?('steps')
errors << "任务 #{job_name} 缺少steps字段"
end
end
end
errors
end
def validate_job_steps(job_name, job_config)
errors = []
steps = job_config['steps'] || []
unless steps.is_a?(Array)
errors << "任务 #{job_name} 的steps必须是数组"
return errors
end
# 验证每个步骤的详细配置
steps.each_with_index do |step, index|
errors.concat(validate_step(job_name, step, index))
end
errors
end
def validate_step(job_name, step, index)
errors = []
unless step.is_a?(Hash)
errors << "任务 #{job_name} 步骤 #{index + 1} 必须是对象"
return errors
end
# unless step.key?('name')
# errors << "任务 #{job_name} 步骤 #{index + 1} 缺少name字段"
# end
# 验证uses或run字段
has_uses = step.key?('uses')
has_run = step.key?('run')
unless has_uses || has_run
errors << "任务 #{job_name} 步骤 #{index + 1} 必须包含uses或run字段"
end
if has_uses && has_run
errors << "任务 #{job_name} 步骤 #{index + 1} 不能同时包含uses和run字段"
end
errors
end
end

View File

@ -63,6 +63,7 @@ module LoginHelper
user.delete_autologin_token(autologin)
user.delete_session_token(session[:tk])
UserOnline.logout(User.current.id)
self.logged_user = nil
end

View File

@ -48,7 +48,7 @@ class JournalsController < ApplicationController
AtmeService.call(current_user, @atme_receivers, journal) if @atme_receivers.size > 0
TouchWebhookJob.set(wait: 5.seconds).perform_later('PullRequestComment', @issue&.id, current_user.id, journal.id, 'created', {})
# @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal")
ProjectActions::PullRequestReviewCommentEvent.build(@issue.project, current_user, @issue.pull_request, journal) if @issue.issue_classify == "pull_request"
# author: zxh
# 调用上链API
push_activity_2_blockchain("issue_comment_create", journal) if Site.has_blockchain? && @project.use_blockchain

View File

@ -5,7 +5,7 @@ class MainController < ApplicationController
skip_before_action :setup_laboratory
def first_stamp
render :json => { status: 0, message: Time.now.to_i }
render :json => { status: 0, message: Time.now.to_i, online: UserOnline.count }
end
def test_404

View File

@ -45,6 +45,7 @@ class OwnersController < ApplicationController
org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @owner.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 == @owner.login) ? projects : projects.visible
@user_projects_count = user_projects.size
@projects_common_count = user_projects.common.size
@projects_mirrior_count = user_projects.mirror.size
@projects_sync_mirrior_count = user_projects.sync_mirror.size

View File

@ -75,7 +75,7 @@ class PullRequestsController < ApplicationController
SendTemplateMessageJob.perform_later('ProjectPullRequest', current_user.id, @pull_request&.id) if Site.has_notice_menu?
Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}"
AtmeService.call(current_user, @atme_receivers, @pull_request) if @atme_receivers.size > 0
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "opened")
# author: zxh
# 调用上链API
push_activity_2_blockchain("pull_request_create", @pull_request) if Site.has_blockchain? && @project.use_blockchain
@ -177,6 +177,7 @@ class PullRequestsController < ApplicationController
# 合并请求下issue处理为关闭
@issue&.update_attributes!({status_id:5})
SendTemplateMessageJob.perform_later('PullRequestClosed', current_user.id, @pull_request.id) if Site.has_notice_menu?
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "closed")
normal_status(1, "已拒绝")
else
normal_status(-1, '合并失败')
@ -247,6 +248,7 @@ class PullRequestsController < ApplicationController
# 合并请求下issue处理为关闭
@issue&.update_attributes!({status_id:5})
ProjectActions::PullRequestEvent.build(@project, current_user, @pull_request, "closed")
SendTemplateMessageJob.perform_later('PullRequestMerged', current_user.id, @pull_request.id) if Site.has_notice_menu?
normal_status(1, "合并成功")
else
@ -324,7 +326,7 @@ class PullRequestsController < ApplicationController
# milestone: 0, #里程碑,未与本地的里程碑关联
}
assignee_login = User.find_by_id(params[:assigned_to_id])&.login
@requests_params = @local_params.merge({assignees: [assignee_login.to_s].reject!(&:empty?)})
@requests_params = @local_params.merge({assignees: [assignee_login.to_s].reject{|item|item.empty?}})
@issue_params = {
author_id: current_user.id,
project_id: @project.id,

View File

@ -84,7 +84,7 @@ class RepositoriesController < ApplicationController
def sub_entries
file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip))
tip_exception('不可访问') if params[:filepath].to_s.include?("chinese_dictionary.txt")
if @project.educoder?
if params[:type] === 'file'
@sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri)
@ -307,6 +307,7 @@ class RepositoriesController < ApplicationController
def raw
domain = GiteaService.gitea_config[:domain]
api_url = GiteaService.gitea_config[:base_url]
tip_exception('不可访问') if params[:filepath].to_s.include?("chinese_dictionary.txt")
url = "/repos/#{@owner.login}/#{@repository.identifier}/raw/#{CGI.escape(params[:filepath])}?ref=#{CGI.escape(params[:ref])}"
file_path = [domain, api_url, url].join

View File

@ -9,7 +9,7 @@ class Users::OrganizationsController < Users::BaseController
@organizations = observed_user.organizations.with_visibility("common")
end
@organizations = @organizations.ransack(login_cont: params[:search]).result if params[:search].present?
@organizations = @organizations.ransack(login_or_nickname_cont: params[:search]).result if params[:search].present?
@home_top_ids = @organizations.joins(:home_top_settings).where(home_top_settings: {user_id: observed_user.id}).order("home_top_settings.created_at asc").pluck(:id)

View File

@ -50,6 +50,7 @@ class UsersController < ApplicationController
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
@user_projects_count = user_projects.size
@projects_common_count = user_projects.common.size
@projects_mirrior_count = user_projects.mirror.size
@projects_sync_mirrior_count = user_projects.sync_mirror.size

View File

@ -56,6 +56,7 @@ class VersionReleasesController < ApplicationController
if params[:attachment_ids].present?
create_attachments(params[:attachment_ids], version_release)
end
ProjectActions::ReleaseEvent.build(@project, current_user, version_release)
normal_status(0, "发布成功")
else
normal_status(-1, "发布失败")

View File

@ -27,6 +27,9 @@ class WatchersController < ApplicationController
def follow
begin
return normal_status(2, "你已关注了") if current_user.watched?(@target)
if @target.is_a?(Project)
ProjectActions::StarEvent.build(@target, current_user)
end
current_user.watch!(@target)
render_ok({watchers_count: @target.watchers_count, watched: true})
rescue Exception => e

View File

@ -14,11 +14,11 @@ module ProjectsHelper
end
def render_zip_url(owner, repository, archive)
[base_url, archive_repositories_path(owner&.login, repository, "#{archive}.zip")].join
[base_url, archive_repositories_path(owner&.login, repository.identifier, "#{archive}.zip")].join
end
def render_tar_url(owner, repository, archive)
[base_url, archive_repositories_path(owner&.login, repository, "#{archive}.tar.gz")].join
[base_url, archive_repositories_path(owner&.login, repository.identifier, "#{archive}.tar.gz")].join
end
def render_download_file_url(owner, repository, filepath, ref)

View File

@ -157,10 +157,12 @@ module RepositoriesHelper
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]
ext = ext.split("?")[0] if ext.include?("?")
ext = ext.gsub(/ \".*\"/, "") if ext.include?(" ")
if (image_type?(ext) || download_type(ext)) && !ext.blank?
s_content = s_content.starts_with?("/") ? s_content[1..-1] : s_content[0..-1]
s_content = File.expand_path(s_content, file_path)
s_content = s_content.split("#{Rails.root}/")[1]
s_content = s_content.gsub(/ \".*\"/, "") if s_content.include?(" ")
next if s_content.blank?
# content = content.gsub(s[0], "/#{s_content}")
join_xxx = s_content.include?("?") ? "&" : "?"
@ -184,8 +186,16 @@ module RepositoriesHelper
content = content.gsub("href=\"#{s[0]}\"", "href=\"#{s_content}\"")
when 'ss_href_1'
content = content.gsub("href=\'#{s[0]}\'", "href=\'#{s_content}\'")
else
content = content.gsub("(#{s[0]})", "(#{s_content})")
else
## md中图片处理包含中文名称tip
if s[0].to_s.include?(" ")
s1 = s[0].to_s.split(" ")
str1 = s1[0]
str2 = s1[1] if s1.size >1
content = content.gsub("(#{str1} #{str2})", "(#{s_content} #{str2})")
else
content = content.gsub("(#{s[0]})", "(#{s_content})")
end
end
else
path = [owner&.login, repo&.identifier, 'tree', ref, file_path].join("/")
@ -203,7 +213,7 @@ module RepositoriesHelper
content = content.gsub("href=\"#{s[0]}\"", "href=\"#{s_content}\"")
when 'ss_href_1'
content = content.gsub("href=\'#{s[0]}\'", "href=\'#{s_content}\'")
else
else
content = content.gsub("(#{s[0]})", "(#{s_content})")
end
end
@ -244,7 +254,9 @@ module RepositoriesHelper
Rails.logger.info("entry===#{entry["type"]} #{entry["name"]}")
content = entry['content'].present? ? entry['content'] : Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
# readme_render_decode64_content(content, owner, repo, ref)
new_readme_render_decode64_content(content, owner, repo, ref, entry['path'], entry['name'])
text = new_readme_render_decode64_content(content, owner, repo, ref, entry['path'], entry['name'])
text = "该内容不合规,请修改。" if text.present? && !HarmoniousDictionary.clean?(text)
text
end
def decode64_content(entry, owner, repo, ref, path=nil)
@ -253,9 +265,13 @@ module RepositoriesHelper
content = entry['content'].present? ? entry['content'] : Gitea::Repository::Entries::GetService.call(owner, repo.identifier, URI.escape(entry['path']), ref: ref)['content']
# Rails.logger.info("content===#{content}")
return Base64.decode64(content).force_encoding("GBK").encode("UTF-8") unless Base64.decode64(content).force_encoding('UTF-8').valid_encoding?
return Base64.decode64(content).force_encoding('UTF-8')
text = Base64.decode64(content).force_encoding('UTF-8')
text = "该内容不合规,请修改。" if text.present? && !HarmoniousDictionary.clean?(text)
text
elsif entry['is_text_file'] == true
return render_decode64_content(entry['content'])
text = render_decode64_content(entry['content'])
text = "该内容不合规,请修改。" if text.present? && !HarmoniousDictionary.clean?(text)
text
else
file_type = File.extname(entry['name'].to_s)[1..-1]
if image_type?(file_type)
@ -264,7 +280,9 @@ module RepositoriesHelper
if download_type(file_type)
return entry['content']
end
render_decode64_content(entry['content'])
text = render_decode64_content(entry['content'])
text = "该内容不合规,请修改。" if text.present? && !HarmoniousDictionary.clean?(text)
text
end
end
@ -305,7 +323,13 @@ module RepositoriesHelper
def repo_git_submodule_url(owner, repo, path)
unless (path.starts_with?('http://') || path.starts_with?('https://'))
path = File.expand_path(path, "/#{owner&.login}/#{repo&.identifier}")
if path.starts_with?('git@code.gitlink.org.cn')
path = path.gsub(":","/").gsub("git@code.gitlink.org.cn","https://gitlink.org.cn")
elsif path.starts_with?('git@code.gitlink.org.cn')
path = path.gsub(":","/").gsub("git@","https://")
else
path = File.expand_path(path, "/#{owner&.login}/#{repo&.identifier}")
end
end
return path

89
app/libs/user_online.rb Normal file
View File

@ -0,0 +1,89 @@
module UserOnline
class << self
redis_config = YAML.load_file(Rails.root.join('config', 'redis.yml'))
if redis_config.present?
redis_url = redis_config[Rails.env]['url']
$redis = Redis.new({:url=>redis_url})
else
$redis = Rails.cache.data
end
def login(user_id)
set_bit(user_id, 1)
end
def logout(user_id)
if Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
now = Time.now()
keys = []
(1..5).each do |i|
key = (now - i.minutes).strftime("%H-%M")
keys << "cache:online_user_#{key}"
set_bit_and_expire(cache_key, user_id, 0) unless $redis.ttl(cache_key) == -2
end
end
set_bit(user_id, 0)
end
def set_bit(user_id, flag)
if $redis.ttl(cache_key) == -2
# 如果key不存在
set_bit_and_expire(cache_key, user_id, flag)
else
if $redis.ttl(cache_key) ==-1
# 可以存在,但过期时间没关联
# 这种情况大概率是并发原因导致对相应key设置过期时跳过了。所以删除并重新设置过期时间
$redis.del(cache_key)
set_bit_and_expire(cache_key, user_id, flag)
end
# 统计当前时间下前5分钟的活跃人数
$redis.setbit(cache_key, user_id, flag)
end
end
def set_bit_and_expire(cache_key, user_id, flag)
$redis.setbit(cache_key, user_id, flag)
# $redis.setbit(last_cache_key, user_id, flag)
# 现在统计5分钟在线人数这里设置6分钟过期目的是统计当前时刻的前5分钟
$redis.expire(cache_key, 6 * 60)
end
def count
if Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
now = Time.now()
keys = []
(0..4).each do |i|
key = (now - i.minutes).strftime("%H-%M")
keys << "cache:online_user_#{key}"
end
# 防止每个分钟数据有重复,所以去重去除
$redis.bitop("OR", "daiao", keys)
$redis.bitcount("daiao")
else
0
end
end
def cache_key
if Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
"cache:online_user_#{Time.now().strftime("%H-%M")}"
else
raise '请配置config.cache_store = redis_store'
end
end
def last_cache_key
if Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
# 如设置5分钟内即300秒有请求的用户视为在线, 则最大会统计到10分钟内有活动用户
# TODO 更精确时长
begin_hour = Time.now.beginning_of_hour
minutes_piece = (Time.now - begin_hour) / (60 * 5)
time = begin_hour.since((minutes_piece.to_i - 1) * (60 * 5)).strftime("%H-%M")
"cache:online_user_#{time}"
else
raise '请配置config.cache_store = redis_store'
end
end
end
end

View File

@ -7,6 +7,7 @@
# content :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
# remote_ip :string(255)
#
# Indexes
#

View File

@ -105,6 +105,24 @@ class Issue < ApplicationRecord
scope :pm_includes, -> {includes(:project, :show_issue_tags, :issue_status, :priority, :version, :user, :show_assigners, :comment_journals, :operate_journals)}
scope :closed, ->{where(status_id: 5)}
scope :opened, ->{where.not(status_id: 5)}
scope :with_assigner_info, -> {
select("DISTINCT issues.*, u.id AS user_id, CONVERT(COALESCE(u.nickname, u.login) USING gbk) AS user_name").
joins("LEFT JOIN (
SELECT ia.issue_id, ia.assigner_id
FROM issue_assigners ia
JOIN (
SELECT issue_id, MIN(id) AS min_assigner_id
FROM issue_assigners
GROUP BY issue_id
) tmp ON ia.issue_id = tmp.issue_id AND ia.id = tmp.min_assigner_id
) assigner ON issues.id = assigner.issue_id
LEFT JOIN users u ON assigner.assigner_id = u.id")
}
scope :ordered_by_assigner, -> (sort_direction) {
order("CASE WHEN u.id IS NULL THEN 1 ELSE 0 END ASC")
.order("CONVERT(COALESCE(u.nickname, u.login) USING gbk) #{sort_direction}")
}
after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
before_save :check_pm_and_update_due_date
after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container, :generate_uuid
@ -124,6 +142,20 @@ class Issue < ApplicationRecord
end
end
def pm_issue_type_chinese_string
case pm_issue_type
when 1
"需求"
when 2
"任务"
when 3
"缺陷"
else
"疑修"
end
end
def destroy_be_pm_links
PmLink.where(be_linkable_type:"Issue",be_linkable_id:self.id).map(&:destroy)
end

View File

@ -110,7 +110,7 @@ class Journal < ApplicationRecord
content += "<b>#{self.issue.subject}</b>"
else
prefix = '将'
prefix += "计划" if self.issue.pm_issue_type == 1
prefix += "需求" if self.issue.pm_issue_type == 1
prefix += "任务" if self.issue.pm_issue_type == 2
prefix += "缺陷" if self.issue.pm_issue_type == 3
prefix += "<b>#{self.issue.subject}</b>"
@ -358,9 +358,9 @@ class Journal < ApplicationRecord
new_value = "未设置" if detail.value.blank?
content += "将工作项类型由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
content.gsub!('1', '需求')
content.gsub!('2', '任务')
content.gsub!('3', '缺陷')
content.gsub!('<b>1</b>', '需求')
content.gsub!('<b>2</b>', '任务')
content.gsub!('<b>3</b>', '缺陷')
return content
when 'pm_sprint_id'
old_value = detail.old_value
@ -483,36 +483,30 @@ class Journal < ApplicationRecord
end
return content
when 'link_issue'
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
if detail.old_value.nil? || detail.old_value.blank?
content += "新建了关联的工作项#{new_value}"
content += "新建了关联的#{new_value}"
else
if detail.value.nil? || detail.value.blank?
content += "删除了关联的工作项#{old_value}"
content += "删除了关联的#{old_value}"
else
content += "新建了关联的工作项#{new_value}"
content += "新建了关联的#{new_value}"
end
end
content.gsub!('1', "需求")
content.gsub!('2', "任务")
content.gsub!('3', "缺陷")
return content
when 'tag_link_issue'
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<b>[#{i.pm_issue_type}]-<#{i.subject}></b>"}.join("")
old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| i.pm_issue_type.nil? && i.pm_project_id.nil? ? "疑修 <b>【#{i.subject}】</b>" : "工作项 <b>[#{i.pm_issue_type_chinese_string}]-<#{i.subject}></b>"}.join("")
if detail.old_value.nil? || detail.old_value.blank?
content += "关联了工作项#{new_value}"
content += "关联了#{new_value}"
else
if detail.value.nil? || detail.value.blank?
content += "取消了关联的工作项#{old_value}"
content += "取消了关联的#{old_value}"
else
content += "关联了工作项#{new_value}"
content += "关联了#{new_value}"
end
end
content.gsub!('1', "需求")
content.gsub!('2', "任务")
content.gsub!('3', "缺陷")
return content
when 'issue'
issue = self.issue
@ -535,6 +529,7 @@ class Journal < ApplicationRecord
case detail.property
when 'issue'
content += "创建了<b>疑修</b>"
return content
when 'attachment'
old_value = Attachment.where("BINARY id in (?) or uuid in (?)", detail.old_value.to_s.split(","), detail.old_value.to_s.split(",")).pluck(:filename).join("")
new_value = Attachment.where("BINARY id in (?) or uuid in (?)", detail.value.to_s.split(","), detail.value.to_s.split(",")).pluck(:filename).join("")
@ -544,6 +539,7 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "将附件由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
when 'issue_tag'
old_value = IssueTag.where(id: detail.old_value.split(",")).pluck(:name).join("")
new_value = IssueTag.where(id: detail.value.split(",")).pluck(:name).join("")
@ -553,6 +549,7 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "将标记由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
when 'assigner'
old_value = User.where(id: detail.old_value.split(",")).map{|u| u.real_name}.join("")
new_value = User.where(id: detail.value.split(",")).map{|u| u.real_name}.join("")
@ -562,6 +559,7 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "将负责人由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
when 'attr'
content += ""
case detail.prop_key
@ -604,8 +602,9 @@ class Journal < ApplicationRecord
new_value = "" if new_value.blank?
content += "由<b>#{old_value}</b>更改为<b>#{new_value}</b>"
end
return content
end
content = self.pm_operate_content if content == ""
end
def journal_content

View File

@ -39,7 +39,7 @@ class Laboratory < ApplicationRecord
validates :identifier, uniqueness: { case_sensitive: false }, allow_nil: true
delegate :name, :navbar, :footer, :login_logo_url, :nav_logo_url, :tab_logo_url, :default_navbar, to: :laboratory_setting
delegate :name, :navbar, :footer, :login_logo_url, :nav_logo_url, :pm_logo_url, :tab_logo_url, :default_navbar, to: :laboratory_setting
def site
rails_env = EduSetting.get('rails_env')

View File

@ -34,7 +34,9 @@ class LaboratorySetting < ApplicationRecord
def nav_logo_url
image_url('nav')
end
def pm_logo_url
image_url('pm_logo')
end
def tab_logo_url
image_url('tab')
end

View File

@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectAction < ApplicationRecord
belongs_to :project
belongs_to :user, optional: true
serialize :event_extra, Hash
end

View File

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::ForkEvent < ProjectAction
def self.build(project, user, fork_project)
project_action = new(project: project, user: user)
project_action.event_extra = {
forkee_id: fork_project.id,
forkee_name: fork_project.identifier,
forkee_owner_id: fork_project.owner.id,
forkee_owner_login: fork_project.owner.login
}.as_json
project_action.save!
end
end

View File

@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::IssueCommentEvent < ProjectAction
def self.build(project, user, issue, journal)
project_action = new(project: project, user: user)
project_action.event_extra = {
issue_id: issue.id,
issue_number: issue.project_issues_index,
body: journal.notes
}.as_json
project_action.save!
end
end

View File

@ -0,0 +1,33 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::IssuesEvent < ProjectAction
def self.build(project, user, issue, action)
project_action = new(project: project, user: user)
project_action.event_extra = {
issue_id: issue.id,
issue_number: issue.project_issues_index,
issue_author_id: issue.author_id,
issue_author_login: issue.user.login,
title: issue.subject,
body: issue.description,
action: action
}.as_json
project_action.save!
end
end

View File

@ -0,0 +1,41 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::PullRequestEvent < ProjectAction
def self.build(project, user, pull_request, action)
project_action = new(project: project, user: user)
gitea_pull_request = Gitea::PullRequest::FilesService.call(project.owner.login, project.identifier, pull_request.gitea_number, project&.owner&.gitea_token)
project_action.event_extra = {
action: action,
pull_id: pull_request.id,
pull_number: pull_request.gitea_number,
title: pull_request.title,
# body: pull_request.body,
# git_diff: gitea_pull_request,
additions: gitea_pull_request['TotalAddition'],
deletions: gitea_pull_request['TotalDeletion'],
changed_files: gitea_pull_request['NumFiles'],
merged: pull_request.status == 1,
base_ref: pull_request.base,
head_repo_id: pull_request.is_original ? pull_request.fork_project_id : pull_request.project_id,
head_repo_name: pull_request.project&.identifier,
head_ref: pull_request.head
}.as_json
project_action.save!
end
end

View File

@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::PullRequestReviewCommentEvent < ProjectAction
def self.build(project, user, pull_request, journal)
project_action = new(project: project, user: user)
project_action.event_extra = {
pull_id: pull_request.id,
pull_number: pull_request.gitea_number,
body: journal.notes
}.as_json
project_action.save!
end
end

View File

@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::ReleaseEvent < ProjectAction
def self.build(project, user, version_release)
project_action = new(project: project, user: user)
project_action.event_extra = {
tag_name: version_release.tag_name,
name: version_release.name,
body: version_release.body
}.as_json
project_action.save!
end
end

View File

@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: project_actions
#
# id :integer not null, primary key
# project_id :integer
# type :string(255)
# user_id :integer
# event_extra :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_project_actions_on_project_id (project_id)
#
class ProjectActions::StarEvent < ProjectAction
def self.build(project, user)
project_action = new(project: project, user: user)
project_action.save!
end
end

View File

@ -144,6 +144,18 @@ class PullRequest < ApplicationRecord
"#{Rails.application.config_for(:configuration)['platform_url']}/#{self.project.owner.login}/#{self.project.name}/pulls/#{self.id}"
end
def closed?
self.status == CLOSED
end
def merged?
self.status == MERGED
end
def opened?
self.status == OPEN
end
def pr_status
case status
when 0

View File

@ -25,17 +25,20 @@
class UserAction < ApplicationRecord
belongs_to :user, optional: true
after_save :add_user_info
serialize :data_bank, JSON
def action_name
case action_type
when "DestroyUser" then "用户注销"
when "DestroyUser" then "删除用户"
when "DestroyProject" then "删除项目"
when "Login" then "登录"
when "Logout" then "退出登录"
when "DestroyOrganization" then "删除组织"
when "Attachment" then "上传附件"
else self.action_type
end
end
@ -43,7 +46,7 @@ class UserAction < ApplicationRecord
def opt_user_name
user = User.find_by(id: self.user_id)
if user.present?
user&.login
user&.real_name
else
del_user = UserAction.find_by(action_type: "DestroyUser", action_id: self.user_id)
del_user.present? ? del_user.user.login : "不存在用户:#{user_id}"
@ -55,6 +58,13 @@ class UserAction < ApplicationRecord
when "DestroyUser" then "账号:#{user&.login}<br/>邮箱:#{user&.mail}<br/>手机号:#{user&.phone}<br/>昵称:#{user&.nickname}<br/>"
when "DestroyProject" then "项目名称:#{project&.name}<br/>项目标识:#{project&.identifier}<br/>"
when "DestroyOrganization" then "组织名称:#{organization&.nickname}<br/>组织标识:#{organization&.login}<br/>"
when 'Attachment'
issue = Issue.find_by_id(attachment.container_id)
if attachment.container_type == "Issue" && issue&.issue_classify == "issue"
return "上传附件issue地址<a href=\"#{Rails.application.config_for(:configuration)['platform_url']}/#{issue&.project&.owner&.login}/#{issue&.project&.identifier}/issues/#{issue&.project_issues_index}\">#{issue&.subject}</a>"
else
return "上传附件地址:<a href=\"#{Rails.application.config_for(:configuration)['platform_url']}/attachments/#{attachment.uuid}\">#{attachment.title}</a>"
end
else "--"
end
end
@ -86,6 +96,15 @@ class UserAction < ApplicationRecord
action_organization
end
def attachment
action_attachment = if action_type == "Attachment" && data_bank.present?
build_mode("Attachment")
else
Attachment.find_by(id: self.action_id)
end
action_attachment
end
def build_mode(model_name)
model = model_name.constantize.new

View File

@ -40,6 +40,7 @@ class Admins::SaveLaboratorySettingService < ApplicationService
save_image_file(params[:nav_logo], 'nav')
save_image_file(params[:login_logo], 'login')
save_image_file(params[:tab_logo], 'tab')
save_image_file(params[:pm_logo], 'pm_logo')
save_image_file(params[:subject_banner], '_subject_banner')
save_image_file(params[:course_banner], '_course_banner')
save_image_file(params[:competition_banner], '_competition_banner')

View File

@ -97,6 +97,9 @@ class Api::V1::Issues::CreateService < ApplicationService
# @信息发送
AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
# 项目行为构建
ProjectActions::IssuesEvent.build(@project, current_user, @created_issue, 'opened')
# 发消息
if Site.has_notice_menu?
SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?

View File

@ -40,7 +40,7 @@ class Api::V1::Issues::Journals::CreateService < ApplicationService
@issue.save!
push_activity_2_blockchain("issue_comment_create", @created_journal) if Site.has_blockchain? && @issue.project&.use_blockchain
ProjectActions::IssueCommentEvent.build(@issue.project, current_user, @issue, @created_journal)
# @信息发送
AtmeService.call(current_user, @atme_receivers, @created_journal) unless receivers_login.blank?
TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueComment', @issue&.id, @current_user.id, @created_journal.id, 'created', {})

View File

@ -9,7 +9,7 @@ class Api::V1::Issues::ListService < ApplicationService
validates :category, inclusion: { in: %w[all opened closed], message: '请输入正确的Category'}
validates :participant_category, inclusion: { in: %w[all aboutme authoredme assignedme atme], message: '请输入正确的ParticipantCategory'}
validates :sort_by, inclusion: { in: %w[issues.created_on issues.updated_on issues.blockchain_token_num issue_priorities.position issues.start_date issues.due_date issues.status_id] , message: '请输入正确的SortBy'}, allow_blank: true
validates :sort_by, inclusion: { in: %w[issues.created_on issues.updated_on issues.blockchain_token_num issue_priorities.position issues.start_date issues.due_date issues.status_id, assigners] , message: '请输入正确的SortBy'}, allow_blank: true
validates :sort_direction, inclusion: { in: %w[asc desc], message: '请输入正确的SortDirection'}, allow_blank: true
validates :current_user, presence: true
@ -175,6 +175,8 @@ class Api::V1::Issues::ListService < ApplicationService
scope = issues.includes(:priority, :issue_status, :user, :show_assigners, :show_issue_tags, :version, :comment_journals)
scope = if sort_by == 'issue_priorities.position'
scope.reorder("issue_priorities.position #{sort_direction}, issues.updated_on DESC").distinct
elsif sort_by == 'assigners'
scope.with_assigner_info.ordered_by_assigner(sort_direction)
else
scope.reorder("#{sort_by} #{sort_direction}").distinct
end

View File

@ -98,6 +98,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
build_previous_issue_changes
build_issue_project_trends if status_id.present? # 开关时间记录
build_project_actions
build_cirle_blockchain_token if blockchain_token_num.present?
unless @project.id.zero?
# @信息发送
@ -185,6 +186,15 @@ class Api::V1::Issues::UpdateService < ApplicationService
end
end
def build_project_actions
if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][1] == 5
ProjectActions::IssuesEvent.build(@project, current_user, @updated_issue, 'closed')
end
if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][0] == 5
ProjectActions::IssuesEvent.build(@project, current_user, @updated_issue, 'reopened')
end
end
def build_after_issue_journal_details
begin
# 更改标题

View File

@ -25,7 +25,9 @@ class Api::V1::Projects::Commits::RecentService < ApplicationService
param = {
access_token: token,
page: page,
limit: limit
limit: limit,
stats: false,
files: false
}
param.merge!(keyword: keyword) if keyword.present?

View File

@ -0,0 +1,39 @@
class Api::V1::Projects::Pulls::ReopenService < ApplicationService
include ActiveModel::Model
attr_reader :project, :pull_request, :issue, :user
def initialize(project, pull_request, user)
@project = project
@pull_request = pull_request
@issue = pull_request&.issue
@user = user
end
def call
raise Error, "参数错误" unless valid?
if gitea_change_status
@pull_request.update_column(:status, PullRequest::OPEN)
@pull_request&.project_trends&.where(action_type: ProjectTrend::CLOSE).destroy_all
@issue.update_column(:status_id, IssueStatus::ADD)
return true
else
raise Error, 'gitea change status failed'
end
end
private
def valid?
return false if @project.blank? || @pull_request.blank? || @issue.blank? || @user.blank?
return false unless @pull_request.closed?
return true
end
def gitea_change_status
gitea_result = Gitea::PullRequest::ChangeStatusService.call(@project.owner.login, @project.identifier, @pull_request.gitea_number, false, @user&.gitea_token)
return true if gitea_result[:status] == :success
return false
end
end

View File

@ -1,14 +1,15 @@
class Api::V1::Users::Feedbacks::CreateService < ApplicationService
include ActiveModel::Model
attr_reader :user, :content
attr_reader :user, :content, :remote_ip
attr_accessor :feedback
validates :content, presence: true
def initialize(user, params)
def initialize(user, params, remote_ip="127.0.0.1")
@user = user
@content = params[:content]
@remote_ip = remote_ip
end
def call
@ -16,6 +17,7 @@ class Api::V1::Users::Feedbacks::CreateService < ApplicationService
begin
@feedback = Feedback.new(user: user, content: content)
@feedback.remote_ip = @remote_ip
@feedback.save!
return @feedback.valid? ? @feedback : nil

View File

@ -0,0 +1,38 @@
# Get a pull request
class Gitea::PullRequest::ChangeStatusService < Gitea::ClientService
attr_reader :owner, :repo, :number, :is_closed, :token
#eq:
# Gitea::PullRequest::ChangeStatusService.call(user.login, repository.identifier, pull.gitea_number, true, user.gitea_token)
def initialize(owner, repo, number, is_closed, token=nil)
@owner = owner
@repo = repo
@number = number
@is_closed = is_closed
@token = token
end
def call
response = get(url, request_params, true)
status, message, body = render_response(response)
json_format(status, message, body)
end
private
def request_params
Hash.new.merge(token: token, is_closed: is_closed)
end
def url
"/repos/#{owner}/#{repo}/pulls/#{number}/change_status".freeze
end
def json_format(status, message, body)
case status
when 204 then success(body)
else
error(message, status)
end
end
end

View File

@ -39,7 +39,8 @@ class Projects::ForkService < ApplicationService
ForkUser.create(project_id: @project.id, fork_project_id: clone_project.id, user_id: clone_project.user_id)
SendTemplateMessageJob.perform_later('ProjectForked', @target_owner.id, @project.id) if Site.has_notice_menu?
ProjectActions::ForkEvent.build(@project, @target_owner, clone_project)
clone_project
end
rescue => e

View File

@ -41,7 +41,7 @@ class Repositories::CreateService < ApplicationService
begin
@gitea_repository = $gitea_client.get_repos_by_owner_repo(project.owner.login, params[:identifier])
rescue Gitea::Api::ServerError => e
if e.http_code.to_i == 404
if e.http_code.to_i == 404 || e.http_code.to_i == 307
if project.owner.is_a?(User)
# @gitea_repository = Gitea::Repository::CreateService.new(user.gitea_token, gitea_repository_params).call
@gitea_repository = $gitea_client.post_user_repos({query: {token: user.gitea_token}, body: gitea_repository_params.to_json})

View File

@ -2,7 +2,7 @@
<% add_admin_breadcrumb('概览', admins_path) %>
<% end %>
<% cache "/admin/dashboards/#{Time.now.strftime('%Y-%m-%d')}", :expires_in => 1.days do %>
<%# cache_if EduSetting.get("open_baidu_tongji").to_s == "true", "/admin/dashboards/#{Time.now.strftime('%Y-%m-%d')}", :expires_in => 1.days do %>
<div class="header bg-gradient-primary pb-8 pt-md-8">
<div class="container-fluid">
<div class="header-body">
@ -32,7 +32,7 @@
</div>
</div>
</div>
<%end %>
<%#end %>
<div class="box admin-list-container project-language-list-container">
<table class="table table-hover text-center subject-list-table">

View File

@ -4,9 +4,12 @@
<th width="5%">序号</th>
<th width="10%">用户名</th>
<th width="20%">用户邮箱</th>
<th width="20%">ip地址</th>
<th width="20%"><%= sort_tag('创建时间', name: 'created_at', path: admins_feedbacks_path) %></th>
<th width="25%">反馈意见</th>
<th width="15%">操作</th>
<!-- 这里是注释内容
<th width="15%">操作</th>
-->
</tr>
</thead>
<tbody>
@ -16,14 +19,17 @@
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td><%= feedback&.user&.login%></td>
<td><%= feedback&.user&.mail%></td>
<td><%= feedback&.remote_ip%></td>
<td><%= feedback.created_at&.strftime('%Y-%m-%d %H:%M') %></td>
<td style="word-wrap: break-word;">
<%= feedback.content %>
</td>
<!-- 这里是注释内容
<td class="action-container">
<%= link_to "发送邮件", new_history_admins_feedback_path(feedback), remote: true, class: "action" %>
<%#= link_to "发送邮件", new_history_admins_feedback_path(feedback), remote: true, class: "action" %>
<%#= link_to "删除", admins_feedback_path(feedback), method: :delete, data:{confirm: "确认删除的吗?"}, class: "action" %>
</td>
-->
</tr>
<% end %>
<% else %>

View File

@ -42,7 +42,7 @@
<div class="setting-item-head"><h6>Logo设置</h6></div>
<div class="dropdown-divider"></div>
<div class="pl-0 py-3 row setting-item-body">
<div class="col-12 col-md-4 logo-item">
<div class="col-12 col-md-3 logo-item">
<% nav_logo_img = setting.nav_logo_url %>
<div class="logo-item-left mr-3 <%= nav_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img nav-logo-img" src="<%= nav_logo_img %>" style="<%= nav_logo_img.present? ? '' : 'display: none' %>"/>
@ -56,7 +56,7 @@
</div>
</div>
<div class="col-12 col-md-4 logo-item">
<div class="col-12 col-md-3 logo-item">
<% login_logo_img = setting.login_logo_url %>
<div class="logo-item-left mr-3 <%= login_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img login-logo-img" src="<%= login_logo_img %>" style="<%= login_logo_img.present? ? '' : 'display: none' %>"/>
@ -70,7 +70,7 @@
</div>
</div>
<div class="col-12 col-md-4 logo-item">
<div class="col-12 col-md-3 logo-item">
<% tab_logo_img = setting.tab_logo_url %>
<div class="logo-item-left mr-3 <%= tab_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img tab-logo-img" src="<%= tab_logo_img %>" style="<%= tab_logo_img.present? ? '' : 'display: none' %>"/>
@ -83,6 +83,21 @@
<div>尺寸16*16 32*32 48*48 64*64 </div>
</div>
</div>
<div class="col-12 col-md-3 logo-item">
<% pm_logo_img = setting.pm_logo_url %>
<div class="logo-item-left mr-3 <%= pm_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img tab-logo-img" src="<%= pm_logo_img %>" style="<%= tab_logo_img.present? ? '' : 'display: none' %>"/>
<%= file_field_tag(:pm_logo, accept: 'image/png,image/jpg,image/jpeg', style: 'display: none', value: params[:pm_logo]) %>
<label for="pm_logo" class="logo-item-upload" data-toggle="tooltip" data-title="选择图片"></label>
</div>
<div class="logo-item-right">
<div class="logo-item-title flex-1">PM管理端logo</div>
<div>格式PNG、JPG</div>
<div>尺寸高度90px以内宽等比例缩放</div>
</div>
</div>
</div>
</div>

View File

@ -26,7 +26,7 @@
<li><%= sidebar_item(admins_faqs_path, 'FAQ', icon: 'question-circle', controller: 'admins-faqs', has_permission: current_user.admin? || current_user.business?) %></li>
<li><%= sidebar_item(admins_nps_path, 'NPS用户调研', icon: 'question-circle', controller: 'admins-nps', has_permission: current_user.admin? || current_user.business?) %></li>
<li><%= sidebar_item(admins_feedbacks_path, '用户反馈', icon: 'question-circle', controller: 'admins-feedbacks', has_permission: current_user.admin? || current_user.business?) %></li>
<li><%= sidebar_item(admins_user_actions_path, '操作记录', icon: 'pencil-square', controller: 'admins-user_actions', has_permission: current_user.admin?) %></li>
<li><%= sidebar_item(admins_user_actions_path, '操作日志', icon: 'pencil-square', controller: 'admins-user_actions', has_permission: current_user.admin?) %></li>
<li><%= sidebar_item(admins_system_notifications_path, '系统公告配置', icon: 'bell', controller: 'admins-system_notifications', has_permission: current_user.admin? || current_user.business?) %></li>
<% end %>
</li>

View File

@ -5,9 +5,9 @@
<div class="box search-form-container user-list-form">
<%= form_tag(admins_user_actions_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
操作类型:
<% action_type_options = [['自定义',''],['用户注销','DestroyUser'], ['删除项目', 'DestroyProject'], ['删除组织', 'DestroyOrganization']] %>
<% action_type_options = [['上传附件', 'Attachment'], ['登录', 'Login'], ['退出登录', 'Logout'],['删除用户','DestroyUser'], ['删除项目', 'DestroyProject'], ['删除组织', 'DestroyOrganization']] %>
<%= select_tag(:action_type_select, options_for_select(action_type_options), class: 'form-control') %>
<%= text_field_tag(:action_type, params[:action_type], class: 'form-control col-sm-2 ml-3',style: 'display:;', placeholder: '自定义操作类型检索') %>
<%= text_field_tag(:action_type, params[:action_type], class: 'form-control col-sm-2 ml-3',style: 'display: none;', placeholder: '自定义操作类型检索') %>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '操作人用户名/邮箱/手机号检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<% end %>

View File

@ -0,0 +1,55 @@
json.(issue, :id, :subject, :project_issues_index)
json.blockchain_token_num (Site.has_blockchain? && issue.project&.use_blockchain) ? issue.blockchain_token_num : nil
json.created_at issue.created_on.strftime("%Y-%m-%d %H:%M")
json.updated_at issue.updated_on.strftime("%Y-%m-%d %H:%M")
json.tags issue.show_issue_tags.each do |tag|
json.partial! "api/v1/issues/issue_tags/simple_detail", locals: {tag: tag}
end
json.status_name issue.issue_status&.name
json.status_id issue.status_id
json.status do
if issue.issue_status.present?
json.partial! "api/v1/issues/statues/simple_detail", locals: {status: issue.issue_status}
else
json.nil!
end
end
json.priority_name issue.priority&.name
json.priority_id issue.priority_id
json.priority do
if issue.priority.present?
json.partial! "api/v1/issues/issue_priorities/simple_detail", locals: {priority: issue.priority}
else
json.nil!
end
end
json.milestone_name issue.version&.name
json.milestone_id issue.fixed_version_id
json.root_id issue.root_id
json.pm_issue_type issue.pm_issue_type
json.pm_sprint_id issue.pm_sprint_id
json.pm_project_id issue.pm_project_id
json.time_scale issue.time_scale
if weekly_begin_date.present? && weekly_end_date.present?
if params[:pm_issue_types].present?
json.child_count issue.children_issues.where(pm_issue_type: 4).where("due_date >=? and start_date <=?", weekly_begin_date, weekly_end_date).count
else
json.child_count issue.children_issues.where("due_date >=? and start_date <=?", weekly_begin_date, weekly_end_date).count
end
else
if params[:pm_issue_types].present?
json.child_count issue.children_issues.where(pm_issue_type: 4).count
else
json.child_count issue.child_count
end
end
json.author do
json.partial! "api/v1/users/simple_user", locals: {user: issue.user}
end
json.assigners issue.show_assigners.each do |assigner|
json.partial! "api/v1/users/simple_user", locals: {user: assigner}
end
json.comment_journals_count issue.comment_journals.size
json.start_date issue.start_date
json.due_date issue.due_date

View File

@ -1,8 +1,8 @@
json.this_week_all_issues @this_week_all_issues.each do |issue|
json.partial! "api/v1/issues/simple_detail", locals: {issue: issue}
json.partial! "api/pm/weekly_issues/simple_detail", locals: {issue: issue, weekly_begin_date: @weekly_begin_date, weekly_end_date: @weekly_end_date}
end
json.this_week_total_count @this_week_all_issues.total_count
json.next_week_all_issues @next_week_all_issues.each do |issue|
json.partial! "api/v1/issues/simple_detail", locals: {issue: issue}
json.partial! "api/pm/weekly_issues/simple_detail", locals: {issue: issue, weekly_begin_date: @weekly_begin_date.to_date+1.week, weekly_end_date: @weekly_end_date.to_date+1.week}
end
json.next_week_total_count @next_week_all_issues.total_count

View File

@ -1,4 +1,11 @@
json.(issue, :id, :subject, :project_issues_index)
json.(issue, :id, :subject, :project_issues_index, :project_id)
json.project do
if issue.project.present?
json.partial! "api/v1/projects/simple_detail", locals: {project: issue.project}
else
json.nil!
end
end
json.blockchain_token_num (Site.has_blockchain? && issue.project&.use_blockchain) ? issue.blockchain_token_num : nil
json.created_at issue.created_on.strftime("%Y-%m-%d %H:%M")
json.updated_at issue.updated_on.strftime("%Y-%m-%d %H:%M")
@ -35,7 +42,6 @@ if params[:pm_issue_types].present?
else
json.child_count issue.child_count
end
json.author do
json.partial! "api/v1/users/simple_user", locals: {user: issue.user}
end

View File

@ -8,7 +8,7 @@ end
if journal.is_journal_detail?
detail = journal.journal_details.take
json.operate_category @issue.pm_issue_type.nil? ? detail.property == "attr" ? detail.prop_key : detail.property : journal.pm_operate_category
json.operate_content journal.is_journal_detail? ? @issue.pm_issue_type.nil? ? journal.operate_content : journal.pm_operate_content : nil
json.operate_content journal.is_journal_detail? ? @issue.pm_project_id.nil? && @issue.pm_issue_type.nil? ? journal.operate_content : journal.pm_operate_content : nil
else
json.notes journal.notes
json.comments_count journal.comments_count

View File

@ -0,0 +1,34 @@
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, :is_public)
json.mirror_url project.repository&.mirror_url
json.type project.numerical_for_project_type
json.praised project.praised_by?(current_user)
json.last_update_time render_unix_time(project.updated_on)
json.full_last_update_time project.updated_on
json.time_ago time_from_now(project.updated_on)
json.language do
if project.project_language.blank?
json.nil!
else
json.id project.project_language.id
json.name project.project_language.name
end
end
json.category do
if project.project_category.blank?
json.nil!
else
json.id project.project_category.id
json.name project.project_category.name
end
end
json.topics project.project_topics.each do |topic|
json.(topic, :id, :name)
end
json.url project.full_url
json.owner do
json.partial! "api/v1/users/simple_user", locals: {user: @organization.team_users.joins(:team).where(teams: {authorize: "owner"}).take&.user}
end
json.team_users_count @organization.team_users.size
end

View File

@ -19,6 +19,7 @@ on:
<% end %>
<%if node.name.to_s.include?("on-pull_request") %>
pull_request:
types: [opened, reopened]
<% end %>
<%if node.name.to_s.include?("on-fork") %>
fork:

View File

@ -12,6 +12,7 @@ elsif @owner.present? && @owner.is_a?(User)
json.undo_events @undo_events
json.user_composes_count @user_composes_count
json.user_org_count @user_org_count
json.user_project_count @user_projects_count
json.common_projects_count @projects_common_count
json.mirror_projects_count @projects_mirrior_count
json.sync_mirror_projects_count @projects_sync_mirrior_count

View File

@ -30,4 +30,6 @@ json.praises project_common["praises"]
json.issues project_common["issues"]
json.pulls project_common["pullrequests"]
json.commits project_common["commits"]
json.issues project_common["issues"]
json.issues project_common["issues"]
json.website project_common['website']
json.lesson_url project_common['project.lesson_url']

View File

@ -31,7 +31,7 @@ json.author do
json.name user.try(:show_real_name)
json.type user&.type
json.login user.login
json.image_url url_to_avatar(user)
json.image_url url_to_avatar(user).present? ? url_to_avatar(user) : User::Avatar.get_letter_avatar_url(user.login)
end
end

View File

@ -18,6 +18,8 @@ json.projects @projects do |project|
json.time_ago time_from_now(project.updated_on)
json.forked_from_project_id project.forked_from_project_id
json.open_devops project.open_devops?
json.website project.website
json.lesson_url project.lesson_url
json.platform project.platform
json.has_dataset project.has_menu_permission("dataset") && project.project_dataset.present?
json.author do

View File

@ -7,6 +7,7 @@ json.commits_count @gitea_pull["commit_num"]
json.files_count @gitea_pull["changed_files"]
json.comments_count @issue.journals.parent_journals.size
json.comments_total_count @issue.get_journals_size
json.can_reopen @pull_request.closed?
json.assign_user do
json.partial! 'users/user_simple', user: @issue_assign_to
end

View File

@ -2,6 +2,7 @@ json.setting do
json.name default_setting.name
json.nav_logo_url default_setting.nav_logo_url&.[](1..-1)
json.login_logo_url default_setting.login_logo_url&.[](1..-1)
json.pm_logo_url default_setting.pm_logo_url&.[](1..-1)
json.tab_logo_url default_setting.tab_logo_url.present? ? default_setting.tab_logo_url&.[](1..-1) : "favicon.ico"
json.site_page_deploy_domain @deploy_domain

View File

@ -4,3 +4,4 @@ json.user_id @user.id
json.image_url url_to_avatar(@user)
json.admin @user.admin
json.email @user.mail
json.phone @user.phone

View File

@ -5,6 +5,7 @@ json.undo_join_projects @common_applied_projects.size
json.undo_events @undo_events
json.user_composes_count @user_composes_count
json.user_org_count @user_org_count
json.user_projects_count @user_projects_count
json.common_projects_count @projects_common_count
json.mirror_projects_count @projects_mirrior_count
json.sync_mirror_projects_count @projects_sync_mirrior_count

View File

@ -1,37 +1,4 @@
习近平
习仲勋
贺国强
贺子珍
周永康
李德生
王岐山
姚依林
回良玉
李源潮
李干成
戴秉国
刘延东
刘瑞龙
俞正声
薄熙来
薄一波
周小川
周建南
温云松
江绵康
李小鹏
李小琳
朱云来
让国人愤怒的第二代身份证
文化大革命
胡海峰
陈良宇
老丁
莱仕德事件
fuck
地下的先烈们纷纷打来电话询问
大纪元
新唐人
淫靡
六四事件
迷昏药
@ -44,8 +11,6 @@ fuck
麻醉乙醚
短信群发器
帝国之梦
毛一鲜
黎阳平
对日强硬
出售枪支
摇头丸
@ -60,8 +25,6 @@ PK黑社会
昏药
侦探设备
麻醉钢枪
反华
官商勾结
升达毕业证
戴海静
自杀指南
@ -72,13 +35,8 @@ PK黑社会
强效失意药
迷奸药
透视眼镜
远程偷拍
自制手枪
激情小电影
黄色小电影
天鹅之旅
盘古乐队
高校暴乱
高校群体事件
大学骚乱
高校骚乱
@ -98,8 +56,6 @@ PK黑社会
反政府
禁书
特码
国民党
贪污
骚妇
法论功
江湖淫娘
@ -152,34 +108,22 @@ AV
玉蒲团
隐形喷剂
反雷达测速
假币
代开发票
假钞
窃听
电话拦截系统
探测狗
手机卡复制器
手机跟踪定位器
监听器
针孔摄像
监听宝
迷幻药
手拍肩
迷魂香
麻古
政治风波
政府软弱
政府无能
九评
十七大代表
暴力拆迁
轮暴致死
暴力镇压
高干子弟名单
性免费电影
全裸
偷拍
中共十七大
徐和柴学友
修炼之歌
警察殴打
@ -187,7 +131,6 @@ AV
李沛瑶遇害
美国凯德
肥东交警
中央军委
针对台湾
梁保华
MC军团
@ -195,7 +138,6 @@ MC军团
伪民运
采花堂
中国劳工通讯
评中国共产党
建立生活性补贴
中共走狗
中共小丑
@ -236,7 +178,6 @@ A集中营
江独裁
李屠夫
江恶人
中共特务
乙醚
党内分裂
圆明网
@ -282,7 +223,6 @@ A集中营
特务民运
中共警察
中共监狱
中共政权
中共迫害
自由联邦
中共独枭
@ -307,9 +247,6 @@ H漫画
买春堂
苏东解体
反右题材
隐私图片
卫星接收器
卫星电视
信号拦截器
新闻通气会
山西洪洞
@ -405,10 +342,6 @@ H漫画
艳照门
奴役童工
性奴
计生风暴
厦门大游行
想不到的黑幕
死亡笔记
二奶
2奶
纪股票市场五卅惨案
@ -427,51 +360,13 @@ H漫画
毛爷爷复活
智能H3
智能H3
赣江学院
江西田园置业集团
海乐神
酣乐欣
高莺莺
完全自杀手册
无界
广东王
口头检查
三句硬话
红海湾
升达
沈阳公安
拦截器
阻无通畅
民为法执
尾行
电车之狼
绕过封锁
本拉登
汕尾事件
公务员工资
公务员调资
鸡吧
公务员的工资
反中游行
支持台湾
双鞋的故事
中国军用运输机
科技精英遇难
湘阴杨林
杨林寨
湘阴县杨林
死刑枪毙
马加爵
死刑过程
学生与警察
鬼村
重题工
先烈的电电
身份证生成
短信猫
车牌反光
次下跪
求救遭拒
邪恶的党
出售迷药
针孔摄像机
@ -483,53 +378,6 @@ H漫画
大揭露
突破封锁
多党执政
生成身份证
华国锋
叶剑英
李先念
汪东兴
韦国清
乌兰夫
刘伯承
许世友
纪登奎
苏振华
余秋里
张廷发
陈永贵
陈锡联
耿飚
聂荣臻
倪志福
徐向前
邓颖超
杨尚昆
杨得志
宋任穷
胡乔木
胡耀邦
廖承志
秦基伟
陈慕华
田纪云
李铁映
李锡铭
杨汝岱
吴学谦
胡启立
芮杏文
杨白冰
邹家华
谭绍文
王汉斌
任建新
于永波
傅全有
王乐泉
王兆国
刘云山
张立昌
张德江
隐形耳机
隐形摄像头
裸聊
@ -589,21 +437,8 @@ a级情片
保钓
鲍戈
鲍彤
暴乱
暴政
北韩
藏独
曹长青
曹刚川
陈炳基
陈破空
陈希同
陈小同
陈宣良
陈一谘
陈总统
程铁军
迟浩田
持不同政见
赤匪
赤化
@ -618,16 +453,11 @@ a级情片
地下教会
地下刊物
电视流氓
钓鱼岛
丁关根
丁子霖
东北独立
东社
东土耳其斯坦
东西南北论坛
动乱
独裁
独裁政治
独夫
独立台湾会
屙民
@ -638,16 +468,11 @@ a级情片
法仑
法伦
法轮
法论
反封锁技术
反腐败论坛
反共
李洪志
法轮功
江泽民
毛泽东
邓小平
胡锦涛
推翻
打倒
台独
@ -672,8 +497,6 @@ a级情片
迷药
强迫
色情
三級
三级
A片
喷精
偷窥
@ -836,7 +659,6 @@ A片
避孕套
你妈逼
大鸡巴
政治
卖春
买春
朱容基
@ -849,8 +671,6 @@ A片
疆独
新疆独立
大盖帽
黑社会
夜总会
妈个
坐台
腐败
@ -869,13 +689,11 @@ A片
吸储
张五常
张丕林
空难
吴邦国
曾庆红
黄菊
吴官正
贾庆林
专制
大紀元
江澤民
朱鎔基
@ -890,44 +708,23 @@ A片
吳邦國
曾慶紅
賈慶林
反人类
反社会
方励之
方舟子
斐得勒
费良勇
分家在
分裂
粉饰太平
风雨神州
风雨神州论坛
封从德
冯东海
冯素英
佛展千手法
付申奇
傅申奇
傅志寰
高文谦
高薪养廉
高瞻
高自联
鸽派
个人崇拜
工自联
共产
共党
共匪
共狗
共军
关卓中
贯通两极法
郭伯雄
郭罗基
郭岩华
国家安全
国家机密
国军
国贼
韩东方
韩联潮
@ -939,10 +736,6 @@ A片
洪传
洪哲胜
胡紧掏
胡锦滔
胡锦淘
胡景涛
胡总书记
护法
花花公子
华建敏
@ -985,67 +778,14 @@ A片
抗议
邝锦文
劳动教养所
劳改
劳教
老江
老毛
黎安友
李洪宽
李继耐
李兰菊
李少民
李淑娴
李旺阳
李文斌
李月月鸟
李志绥
连胜德
廉政大论坛
梁光烈
梁擎墩
两岸关系
两岸三地论坛
两个中国
廖锡龙
林保华
林长盛
林樵清
林慎立
刘宾深
刘宾雁
刘国凯
刘华清
刘俊国
刘凯中
刘千石
刘山青
刘士贤
刘文胜
刘晓波
刘晓竹
刘永川
龙虎豹
陆委会
吕京花
吕秀莲
抡功
轮大
罗礼诗
马大维
马良骏
马三家
马时敏
卖国
毛厕洞
毛贼东
美国参考
美国之音
蒙独
蒙古独立
密穴
绵恒
民国
民进党
民联
民意
民意论坛
@ -1073,7 +813,6 @@ A片
人民内情真相
人民真实
人民之声论坛
人权
善恶有报
上海帮
邵家健
@ -1087,14 +826,6 @@ A片
双十节
水扁
税力
司马晋
司马璐
司徒华
斯诺
四川独立
宋书元
苏绍智
苏晓康
台盟
台湾狗
台湾建国运动组织
@ -1110,36 +841,8 @@ A片
统独
统独论坛
统战
屠杀
外交与方略
万润南
万晓东
王宝森
王炳章
王超华
王辅臣
王涵万
王沪宁
王军涛
王力雄
王瑞林
王润生
王若望
王希哲
王秀丽
王冶坪
网特
魏新生
温元凯
文革
无界浏览器
吴百益
吴方城
吴弘达
吴宏达
吴仁华
吴学灿
吴学璨
吾尔开希
五不
洗脑
@ -1207,12 +910,10 @@ A片
正义党论坛
换妻
真木加美
超爽
毛片
打手枪
欲望
脚交
伦理
艳星
三陪
绝色
@ -1239,7 +940,6 @@ A片
天安门一代
自民党
自由民主论坛
走私
淫穴
幼女
舆论
@ -1252,7 +952,6 @@ fa轮
指点江山论坛
中俄边界
中功
中共
中国威胁论
中国问题论坛
中国之春
@ -1275,7 +974,6 @@ fa轮
狗卵子
我操你
狗b
法功
群奸
狗操
猪操
@ -1357,7 +1055,6 @@ B样
淫图
交媾
护士
自拍
大片
夫妻
流血

View File

@ -1,7 +1,5 @@
6-4tianwang
89-64cdjp
ADMIN
Administrator
asshole
BLOWJOB
chinaliberal
@ -12,7 +10,6 @@ creaders
Crestbone
dajiyuan
dfdz
DICK
falun
falundafa
Feelmistone
@ -22,7 +19,6 @@ fuck
gcd
Gruepin
Guichuideng
HACKING
hongzhi
hrichina
HUANET
@ -31,14 +27,9 @@ incest
jiangdongriji
jiaochuang
jiaochun
KEFU
KISSMYASS
lihongzhi
minghui
minghuinews
nacb
Neckromancer
NMIS
PAPER64
penis
qiangjian

View File

@ -1,8 +1,6 @@
---
- 6-4tianwang
- 89-64cdjp
- ADMIN
- Administrator
- asshole
- BLOWJOB
- chinaliberal
@ -13,7 +11,6 @@
- Crestbone
- dajiyuan
- dfdz
- DICK
- falun
- falundafa
- Feelmistone
@ -23,7 +20,6 @@
- gcd
- Gruepin
- Guichuideng
- HACKING
- hongzhi
- hrichina
- HUANET
@ -32,14 +28,9 @@
- jiangdongriji
- jiaochuang
- jiaochun
- KEFU
- KISSMYASS
- lihongzhi
- minghui
- minghuinews
- nacb
- Neckromancer
- NMIS
- PAPER64
- penis
- qiangjian

View File

@ -67,9 +67,15 @@ defaults format: :json do
collection do
post :check_user_id
post :check_user_login
post 'access_token', to: 'user_tokens#show'
end
end
scope 'organizations/:owner', module: :organizations do
resources :sync_repositories, only: [:create]
resources :projects, only: [:index]
end
scope ':owner' do
resource :users, path: '/', only: [:update, :edit, :destroy] do
collection do
@ -188,6 +194,7 @@ defaults format: :json do
resources :runs, only: [:index, :create] do
post '/jobs/:job', to: 'runs#job_show'
post '/rerun', to: 'runs#rerun'
post '/cancel', to: 'runs#cancel'
post '/jobs/:job/rerun', to: 'runs#job_rerun'
get '/jobs/:job/logs', to: 'runs#job_logs'
end
@ -203,6 +210,7 @@ defaults format: :json do
resources :pulls, module: 'pulls' do
member do
get :files
post :reopen
end
resources :versions, only: [:index] do
member do

View File

@ -0,0 +1,5 @@
class AddProjectIndexGpid < ActiveRecord::Migration[5.2]
def change
add_index :projects, :gpid
end
end

View File

@ -0,0 +1,12 @@
class CreateProjectActions < ActiveRecord::Migration[5.2]
def change
create_table :project_actions do |t|
t.references :project
t.string :type
t.integer :user_id
t.text :event_extra
t.timestamps
end
end
end

View File

@ -0,0 +1,5 @@
class AddUserIndexGiteaToken < ActiveRecord::Migration[5.2]
def change
add_index :users, :gitea_token
end
end

View File

@ -0,0 +1,5 @@
class AddRemoteIpToFeedbacks < ActiveRecord::Migration[5.2]
def change
add_column :feedbacks, :remote_ip, :string
end
end

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More