diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index de5e0a8c..3aa98257 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -217,7 +217,7 @@ class AttachmentsController < ApplicationController if @file.container && current_user.logged? if @file.container.is_a?(Issue) course = @file.container.project - candown = course.member?(current_user) + candown = course.member?(current_user) || course.is_public elsif @file.container.is_a?(Journal) course = @file.container.issue.project candown = course.member?(current_user) diff --git a/app/controllers/compare_controller.rb b/app/controllers/compare_controller.rb index 07691d79..382e7506 100644 --- a/app/controllers/compare_controller.rb +++ b/app/controllers/compare_controller.rb @@ -16,6 +16,7 @@ class CompareController < ApplicationController if @base.blank? || @head.blank? return -2, "请选择分支" else + return -2, "目标仓库未开启合并请求(PR)功能" unless @project.has_menu_permission("pulls") if @head.include?(":") fork_project = @project.forked_projects.joins(:owner).where(users: {login: @head.to_s.split("/")[0]}).take return -2, "请选择正确的仓库" unless fork_project.present? @@ -50,6 +51,6 @@ class CompareController < ApplicationController end def gitea_compare(base, head) - Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, CGI.escape(base), CGI.escape(head), current_user.gitea_token) + Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, Addressable::URI.escape(base), Addressable::URI.escape(head), current_user.gitea_token) end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index ad53a4b3..b546ea71 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -24,7 +24,7 @@ class IssuesController < ApplicationController @filter_issues = @all_issues @filter_issues = @filter_issues.where.not(status_id: IssueStatus::CLOSED) if params[:status_type].to_i == IssueStatus::ADD @filter_issues = @filter_issues.where(status_id: IssueStatus::CLOSED) if params[:status_type].to_i == IssueStatus::SOLVING - @filter_issues = @filter_issues.where("subject LIKE ? OR description LIKE ? ", "%#{params[:search]}%", "%#{params[:search]}%") if params[:search].present? + @filter_issues = @filter_issues.where("issues.subject LIKE ? OR issues.description LIKE ? ", "%#{params[:search]}%", "%#{params[:search]}%") if params[:search].present? @open_issues = @all_issues.where.not(status_id: IssueStatus::CLOSED) @close_issues = @all_issues.where(status_id: IssueStatus::CLOSED) scopes = Issues::ListQueryService.call(issues,params.delete_if{|k,v| v.blank?}, "Issue") diff --git a/app/controllers/organizations/organization_users_controller.rb b/app/controllers/organizations/organization_users_controller.rb index 1cddabc9..5f358858 100644 --- a/app/controllers/organizations/organization_users_controller.rb +++ b/app/controllers/organizations/organization_users_controller.rb @@ -5,7 +5,11 @@ class Organizations::OrganizationUsersController < Organizations::BaseController def index @organization_users = @organization.organization_users.includes(:user) search = params[:search].to_s.downcase - @organization_users = @organization_users.joins(:user).merge(User.like(search)) + user_condition_users = User.like(search).to_sql + team_condition_teams = User.joins(:teams).merge(@organization.teams.like(search)).to_sql + users = User.from("( #{user_condition_users} UNION #{team_condition_teams }) AS users") + + @organization_users = @organization_users.where(user_id: users).distinct @organization_users = kaminari_paginate(@organization_users) end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c31136f8..be9a93d9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -135,7 +135,7 @@ class ProjectsController < ApplicationController validate_params = project_params.slice(:name, :description, :project_category_id, :project_language_id, :private, :identifier) - Projects::UpdateForm.new(validate_params.merge(user_id: @project.user_id, project_identifier: @project.identifier)).validate! + Projects::UpdateForm.new(validate_params.merge(user_id: @project.user_id, project_identifier: @project.identifier, project_name: @project.name)).validate! private = @project.forked_from_project.present? ? !@project.forked_from_project.is_public : params[:private] || false diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index 2417e4fd..189c5c6b 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -2,7 +2,7 @@ class PullRequestsController < ApplicationController before_action :require_login, except: [:index, :show, :files, :commits] before_action :require_profile_completed, only: [:create] before_action :load_repository - before_action :check_menu_authorize + before_action :check_menu_authorize, only: [:index, :show, :create, :update, :refuse_merge, :pr_merge] before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos, :files, :commits] before_action :load_pull_request, only: [:files, :commits] before_action :find_atme_receivers, only: [:create, :update] @@ -16,7 +16,7 @@ class PullRequestsController < ApplicationController issues = issues.where(is_private: false) unless current_user.present? && (current_user.admin? || @project.member?(current_user)) @all_issues = issues.distinct @filter_issues = @all_issues - @filter_issues = @filter_issues.where("subject LIKE ? OR description LIKE ? ", "%#{params[:search]}%", "%#{params[:search]}%") if params[:search].present? + @filter_issues = @filter_issues.where("issues.subject LIKE ? OR issues.description LIKE ? ", "%#{params[:search]}%", "%#{params[:search]}%") if params[:search].present? @open_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::OPEN}) @close_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::CLOSED}) @merged_issues = @filter_issues.joins(:pull_request).where(pull_requests: {status: PullRequest::MERGED}) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index c42051e0..9fef22fc 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -1,390 +1,390 @@ -class RepositoriesController < ApplicationController - include RepositoriesHelper - include ApplicationHelper - include OperateProjectAbilityAble - include Repository::LanguagesPercentagable - - before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror] - before_action :require_profile_completed, only: [:create_file] - before_action :load_repository - before_action :authorizate!, except: [:sync_mirror, :tags, :commit, :archive] - before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror] - before_action :get_ref, only: %i[entries sub_entries top_counts file archive] - before_action :get_latest_commit, only: %i[entries sub_entries top_counts] - before_action :get_statistics, only: %i[top_counts] - - def files - result = @project.educoder? ? nil : Gitea::Repository::Files::GetService.call(@owner, @project.identifier, @ref, params[:search], @owner.gitea_token) - render json: result - end - - # 新版项目详情 - def detail - @user = current_user - @result = Repositories::DetailService.call(@owner, @repository, @user) - @project_fork_id = @project.try(:forked_from_project_id) - if @project_fork_id.present? - @fork_project = Project.find_by(id: @project_fork_id) - @fork_project_user = @fork_project.owner - end - rescue Exception => e - uid_logger_error(e.message) - tip_exception(e.message) - end - - def show - @user = current_user - @repo = @project.repository - @result = @project.forge? ? Gitea::Repository::GetService.new(@owner, @project.identifier).call : nil - @project_fork_id = @project.try(:forked_from_project_id) - if @project_fork_id.present? - @fork_project = Project.find_by(id: @project_fork_id) - @fork_project_user = @fork_project.owner - end - rescue Exception => e - uid_logger_error(e.message) - tip_exception(e.message) - end - - def entries - @project.increment!(:visits) - CacheAsyncSetJob.perform_later("project_common_service", {visits: 1}, @project.id) - if @project.educoder? - @entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name) - else - @entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call - @entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : [] - @path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" - end - end - - def top_counts - @result = @project.educoder? ? nil : Gitea::Repository::GetService.new(@project.owner, @project.identifier).call - end - - def sub_entries - file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) - - if @project.educoder? - if params[:type] === 'file' - @sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri) - logger.info "######### sub_entries: #{@sub_entries}" - return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1 - - tmp_entries = { - "content" => @sub_entries['data']['content'], - "type" => "blob" - } - @sub_entries = { - "trees"=>tmp_entries, - "commits" => [{}] - } - else - begin - @sub_entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder&.repo_name, {path: file_path_uri}) - if @sub_entries.blank? || @sub_entries['status'].to_i === -1 - @sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri) - return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1 - tmp_entries = { - "content" => @sub_entries['data']['content'], - "type" => "blob" - } - @sub_entries = { - "trees"=>tmp_entries, - "commits" => [{}] - } - end - rescue - return render_error('该文件暂未开放,敬请期待.') - end - end - else - @path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" - interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, file_path_uri, ref: @ref) - if interactor.success? - result = interactor.result - @sub_entries = result.is_a?(Array) ? result.sort_by{ |hash| hash['type'] } : result - else - render_error(interactor.error) - end - end - end - - def commits - if @project.educoder? - @commits = Educoder::Repository::Commits::ListService.call(@project&.project_educoder&.repo_name) - else - if params[:filepath].present? - file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) - @hash_commit = Gitea::Repository::Commits::FileListService.new(@owner.login, @project.identifier, file_path_uri, - sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call - else - @hash_commit = Gitea::Repository::Commits::ListService.new(@owner.login, @project.identifier, - sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call - end - end - end - - def commits_slice - @hash_commit = Gitea::Repository::Commits::ListSliceService.call(@owner.login, @project.identifier, - sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token) - end - - def commit - @sha = params[:sha] - if @project.educoder? - return render_error('暂未开放,敬请期待.') - else - @commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token) - @commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token, {diff: true}) - end - end - - def tags - result = Gitea::Repository::Tags::ListService.call(current_user&.gitea_token, @owner.login, @project.identifier, {page: params[:page], limit: params[:limit]}) - - @tags = result.is_a?(Hash) && result.key?(:status) ? [] : result - end - - def contributors - if params[:filepath].present? || @project.educoder? - @contributors = [] - else - result = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier) - @contributors = result.is_a?(Hash) && result.key?(:status) ? [] : result - end - rescue - @contributors = [] - end - - def edit - return render_forbidden if !@project.manager?(current_user) && !current_user.admin? - end - - def create_file - interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params) - if interactor.success? - @file = interactor.result - # create_new_pr(params) - #如果是更新流水线文件 - if params[:pipeline_id] - update_pipeline(params[:pipeline_id]) - end - else - render_error(interactor.error) - end - end - - def update_pipeline(pipeline_id) - pipeline = Ci::Pipeline.find(pipeline_id) - if pipeline - pipeline.update!(sync: 1) - end - end - - def update_file - interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) - if interactor.success? - @file = interactor.result - # TODO: 是否创建pr - # create_new_pr(params) - render_result(1, "更新成功") - else - render_error(interactor.error) - end - end - - def delete_file - interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) - if interactor.success? - @file = interactor.result - render_result(1, "文件删除成功") - else - render_error(interactor.error) - end - end - - def repo_hook - - end - - def sync_mirror - return render_error("正在镜像中..") if @repository.mirror.waiting? - - @repository.sync_mirror! - SyncMirroredRepositoryJob.perform_later(@repository.id, current_user.id) - render_ok - end - - def readme - if params[:filepath].present? - result = Gitea::Repository::Readme::DirService.call(@owner.login, @repository.identifier, params[:filepath], params[:ref], current_user&.gitea_token) - else - result = Gitea::Repository::Readme::GetService.call(@owner.login, @repository.identifier, params[:ref], current_user&.gitea_token) - end - @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") - rescue - render json: nil - end - - def languages - if @project.educoder? - render json: {} - else - render json: languages_precentagable - end - end - - def archive - domain = Gitea.gitea_config[:domain] - api_url = Gitea.gitea_config[:base_url] - archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{CGI.escape(params[:archive])}" - - file_path = [domain, api_url, archive_url].join - file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("?") if @repository.hidden? - - return render_not_found if !request.format.zip? && !request.format.gzip? - - redirect_to file_path - end - - def raw - domain = Gitea.gitea_config[:domain] - api_url = Gitea.gitea_config[:base_url] - - url = "/repos/#{@owner.login}/#{@repository.identifier}/raw/#{CGI.escape(params[:filepath])}?ref=#{CGI.escape(params[:ref])}" - file_path = [domain, api_url, url].join - file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&") - - redirect_to file_path - end - - private - - def find_project - @project = Project.find params[:id] - render_not_found("未找到相关的仓库") unless @project - end - - def find_project_with_includes - @project = Project.includes(:repository, :owner, :watchers, :praise_treads).find params[:id] - end - - def authorizate! - return if current_user && current_user.admin? - if @project.repository.hidden? && !@project.member?(current_user) - render_forbidden - end - end - - # TODO 获取最新commit信息 - def project_commits - if params[:filepath].present? - file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) - Gitea::Repository::Commits::FileListService.new(@project.owner.login, @project.identifier, file_path_uri, - sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call - else - Gitea::Repository::Commits::ListService.new(@project.owner.login, @project.identifier, - sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call - end - end - - def get_statistics - @branches_count = @project.educoder? ? 0 : Gitea::Repository::Branches::ListService.new(@project.owner, @project.identifier).call&.size - @tags_count = @project.educoder? ? 0 : Gitea::Repository::Tags::ListService.new(current_user&.gitea_token, @project.owner.login, @project.identifier).call&.size - end - - def get_ref - @ref = params[:ref] || @project&.default_branch - end - - def get_latest_commit - latest_commit = @project.educoder? ? nil : project_commits - @latest_commit = latest_commit.present? ? latest_commit[:body][0] : nil - @commits_count = latest_commit.present? ? latest_commit[:total_count] : 0 - end - - def content_params - { - filepath: params[:filepath], - branch: params[:branch], - new_branch: params[:new_branch], - content: params[:content], - message: params[:message], - committer: { - email: current_user.mail, - name: current_user.login - }, - identifier: @project.identifier - } - end - - def hook_params(hook_type, params) - # if hook_type == "push" - # # TODO hook返回的记录中,暂时没有文件代码数量的增减,暂时根据 commits数量来计算 - # uploadPushInfo = { - # "sha": params["commits"].present? ? params["commits"].last : "", - # "branch": params["ref"].to_s.split("/").last, - # "modification_lines": params["commits"].length - # } - # elsif hook_type == "pull_request" && params["action"].to_s == "closed" #合并请求合并后才会有上链操作 - # uploadPushInfo = { - # "branch": params["base"]["ref"].to_s.split("/").last, - # "sha": params["pull_request"]["merge_base"], - # "modification_lines": 1 #pull_request中没有commits数量 - # } - # else - # uploadPushInfo = {} - # end - - # uploadPushInfo - end - - def create_new_pr(params) - if params[:new_branch].present? && params[:new_branch] != params[:branch] - local_params = { - title: params[:message], #标题 - body: params[:content], #内容 - head: params[:new_branch], #源分支 - base: params[:branch], #目标分支 - milestone: 0 #里程碑,未与本地的里程碑关联 - - } - requests_params = local_params.merge({ - assignee: current_user.try(:login), - assignees: [], - labels: [], - due_date: Time.now - }) - - issue_params = { - author_id: current_user.id, - project_id: @project.id, - subject: params[:message], - description: params[:content], - assigned_to_id: nil, - fixed_version_id: nil, - issue_tags_value: nil, - issue_classify: "pull_request", - issue_type: "1", - tracker_id: 2, - status_id: 1, - priority_id: params[:priority_id] || "2" - } - @pull_issue = Issue.new(issue_params) - if @pull_issue.save! - local_requests = PullRequest.new(local_params.merge(user_id: current_user.try(:id), project_id: @project.id, issue_id: @pull_issue.id)) - if local_requests.save - gitea_request = Gitea::PullRequest::CreateService.new(current_user.try(:gitea_token), @owner.login, @project.try(:identifier), requests_params).call - if gitea_request[:status] == :success && local_requests.update_attributes(gpid: gitea_request["body"]["number"]) - local_requests.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") - end - end - end - end - end - -end +class RepositoriesController < ApplicationController + include RepositoriesHelper + include ApplicationHelper + include OperateProjectAbilityAble + include Repository::LanguagesPercentagable + + before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror] + before_action :require_profile_completed, only: [:create_file] + before_action :load_repository + before_action :authorizate!, except: [:sync_mirror, :tags, :commit, :archive] + before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror] + before_action :get_ref, only: %i[entries sub_entries top_counts file archive] + before_action :get_latest_commit, only: %i[entries sub_entries top_counts] + before_action :get_statistics, only: %i[top_counts] + + def files + result = @project.educoder? ? nil : Gitea::Repository::Files::GetService.call(@owner, @project.identifier, @ref, params[:search], @owner.gitea_token) + render json: result + end + + # 新版项目详情 + def detail + @user = current_user + @result = Repositories::DetailService.call(@owner, @repository, @user) + @project_fork_id = @project.try(:forked_from_project_id) + if @project_fork_id.present? + @fork_project = Project.find_by(id: @project_fork_id) + @fork_project_user = @fork_project.owner + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def show + @user = current_user + @repo = @project.repository + @result = @project.forge? ? Gitea::Repository::GetService.new(@owner, @project.identifier).call : nil + @project_fork_id = @project.try(:forked_from_project_id) + if @project_fork_id.present? + @fork_project = Project.find_by(id: @project_fork_id) + @fork_project_user = @fork_project.owner + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def entries + @project.increment!(:visits) + CacheAsyncSetJob.perform_later("project_common_service", {visits: 1}, @project.id) + if @project.educoder? + @entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name) + else + @entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call + @entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : [] + @path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" + end + end + + def top_counts + @result = @project.educoder? ? nil : Gitea::Repository::GetService.new(@project.owner, @project.identifier).call + end + + def sub_entries + file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) + + if @project.educoder? + if params[:type] === 'file' + @sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri) + logger.info "######### sub_entries: #{@sub_entries}" + return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1 + + tmp_entries = { + "content" => @sub_entries['data']['content'], + "type" => "blob" + } + @sub_entries = { + "trees"=>tmp_entries, + "commits" => [{}] + } + else + begin + @sub_entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder&.repo_name, {path: file_path_uri}) + if @sub_entries.blank? || @sub_entries['status'].to_i === -1 + @sub_entries = Educoder::Repository::Entries::GetService.call(@project&.project_educoder&.repo_name, file_path_uri) + return render_error('该文件暂未开放,敬请期待.') if @sub_entries['status'].to_i === -1 + tmp_entries = { + "content" => @sub_entries['data']['content'], + "type" => "blob" + } + @sub_entries = { + "trees"=>tmp_entries, + "commits" => [{}] + } + end + rescue + return render_error('该文件暂未开放,敬请期待.') + end + end + else + @path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" + interactor = Repositories::EntriesInteractor.call(@owner, @project.identifier, file_path_uri, ref: @ref) + if interactor.success? + result = interactor.result + @sub_entries = result.is_a?(Array) ? result.sort_by{ |hash| hash['type'] } : result + else + render_error(interactor.error) + end + end + end + + def commits + if @project.educoder? + @commits = Educoder::Repository::Commits::ListService.call(@project&.project_educoder&.repo_name) + else + if params[:filepath].present? + file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) + @hash_commit = Gitea::Repository::Commits::FileListService.new(@owner.login, @project.identifier, file_path_uri, + sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call + else + @hash_commit = Gitea::Repository::Commits::ListService.new(@owner.login, @project.identifier, + sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token).call + end + end + end + + def commits_slice + @hash_commit = Gitea::Repository::Commits::ListSliceService.call(@owner.login, @project.identifier, + sha: params[:sha], page: params[:page], limit: params[:limit], token: current_user&.gitea_token) + end + + def commit + @sha = params[:sha] + if @project.educoder? + return render_error('暂未开放,敬请期待.') + else + @commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token) + @commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, current_user&.gitea_token, {diff: true}) + end + end + + def tags + result = Gitea::Repository::Tags::ListService.call(current_user&.gitea_token, @owner.login, @project.identifier, {page: params[:page], limit: params[:limit]}) + + @tags = result.is_a?(Hash) && result.key?(:status) ? [] : result + end + + def contributors + if params[:filepath].present? || @project.educoder? + @contributors = [] + else + result = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier) + @contributors = result.is_a?(Hash) && result.key?(:status) ? [] : result + end + rescue + @contributors = [] + end + + def edit + return render_forbidden if !@project.manager?(current_user) && !current_user.admin? + end + + def create_file + interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params) + if interactor.success? + @file = interactor.result + # create_new_pr(params) + #如果是更新流水线文件 + if params[:pipeline_id] + update_pipeline(params[:pipeline_id]) + end + else + render_error(interactor.error) + end + end + + def update_pipeline(pipeline_id) + pipeline = Ci::Pipeline.find(pipeline_id) + if pipeline + pipeline.update!(sync: 1) + end + end + + def update_file + interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) + if interactor.success? + @file = interactor.result + # TODO: 是否创建pr + # create_new_pr(params) + render_result(1, "更新成功") + else + render_error(interactor.error) + end + end + + def delete_file + interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) + if interactor.success? + @file = interactor.result + render_result(1, "文件删除成功") + else + render_error(interactor.error) + end + end + + def repo_hook + + end + + def sync_mirror + return render_error("正在镜像中..") if @repository.mirror.waiting? + + @repository.sync_mirror! + SyncMirroredRepositoryJob.perform_later(@repository.id, current_user.id) + render_ok + end + + def readme + if params[:filepath].present? + result = Gitea::Repository::Readme::DirService.call(@owner.login, @repository.identifier, params[:filepath], params[:ref], current_user&.gitea_token) + else + result = Gitea::Repository::Readme::GetService.call(@owner.login, @repository.identifier, params[:ref], current_user&.gitea_token) + end + @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") + rescue + render json: nil + end + + def languages + if @project.educoder? + render json: {} + else + render json: languages_precentagable + end + end + + def archive + domain = Gitea.gitea_config[:domain] + api_url = Gitea.gitea_config[:base_url] + archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{Addressable::URI.escape(params[:archive])}" + + file_path = [domain, api_url, archive_url].join + file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("?") if @repository.hidden? + + return render_not_found if !request.format.zip? && !request.format.gzip? + + redirect_to file_path + end + + def raw + domain = Gitea.gitea_config[:domain] + api_url = Gitea.gitea_config[:base_url] + + url = "/repos/#{@owner.login}/#{@repository.identifier}/raw/#{Addressable::URI.escape(params[:filepath])}?ref=#{Addressable::URI.escape(params[:ref])}" + file_path = [domain, api_url, url].join + file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&") + + redirect_to file_path + end + + private + + def find_project + @project = Project.find params[:id] + render_not_found("未找到相关的仓库") unless @project + end + + def find_project_with_includes + @project = Project.includes(:repository, :owner, :watchers, :praise_treads).find params[:id] + end + + def authorizate! + return if current_user && current_user.admin? + if @project.repository.hidden? && !@project.member?(current_user) + render_forbidden + end + end + + # TODO 获取最新commit信息 + def project_commits + if params[:filepath].present? + file_path_uri = URI.parse(URI.encode(params[:filepath].to_s.strip)) + Gitea::Repository::Commits::FileListService.new(@project.owner.login, @project.identifier, file_path_uri, + sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call + else + Gitea::Repository::Commits::ListService.new(@project.owner.login, @project.identifier, + sha: get_ref, page: 1, limit: 1, token: current_user&.gitea_token).call + end + end + + def get_statistics + @branches_count = @project.educoder? ? 0 : Gitea::Repository::Branches::ListService.new(@project.owner, @project.identifier).call&.size + @tags_count = @project.educoder? ? 0 : Gitea::Repository::Tags::ListService.new(current_user&.gitea_token, @project.owner.login, @project.identifier).call&.size + end + + def get_ref + @ref = params[:ref] || @project&.default_branch + end + + def get_latest_commit + latest_commit = @project.educoder? ? nil : project_commits + @latest_commit = latest_commit.present? ? latest_commit[:body][0] : nil + @commits_count = latest_commit.present? ? latest_commit[:total_count] : 0 + end + + def content_params + { + filepath: params[:filepath], + branch: params[:branch], + new_branch: params[:new_branch], + content: params[:content], + message: params[:message], + committer: { + email: current_user.mail, + name: current_user.login + }, + identifier: @project.identifier + } + end + + def hook_params(hook_type, params) + # if hook_type == "push" + # # TODO hook返回的记录中,暂时没有文件代码数量的增减,暂时根据 commits数量来计算 + # uploadPushInfo = { + # "sha": params["commits"].present? ? params["commits"].last : "", + # "branch": params["ref"].to_s.split("/").last, + # "modification_lines": params["commits"].length + # } + # elsif hook_type == "pull_request" && params["action"].to_s == "closed" #合并请求合并后才会有上链操作 + # uploadPushInfo = { + # "branch": params["base"]["ref"].to_s.split("/").last, + # "sha": params["pull_request"]["merge_base"], + # "modification_lines": 1 #pull_request中没有commits数量 + # } + # else + # uploadPushInfo = {} + # end + + # uploadPushInfo + end + + def create_new_pr(params) + if params[:new_branch].present? && params[:new_branch] != params[:branch] + local_params = { + title: params[:message], #标题 + body: params[:content], #内容 + head: params[:new_branch], #源分支 + base: params[:branch], #目标分支 + milestone: 0 #里程碑,未与本地的里程碑关联 + + } + requests_params = local_params.merge({ + assignee: current_user.try(:login), + assignees: [], + labels: [], + due_date: Time.now + }) + + issue_params = { + author_id: current_user.id, + project_id: @project.id, + subject: params[:message], + description: params[:content], + assigned_to_id: nil, + fixed_version_id: nil, + issue_tags_value: nil, + issue_classify: "pull_request", + issue_type: "1", + tracker_id: 2, + status_id: 1, + priority_id: params[:priority_id] || "2" + } + @pull_issue = Issue.new(issue_params) + if @pull_issue.save! + local_requests = PullRequest.new(local_params.merge(user_id: current_user.try(:id), project_id: @project.id, issue_id: @pull_issue.id)) + if local_requests.save + gitea_request = Gitea::PullRequest::CreateService.new(current_user.try(:gitea_token), @owner.login, @project.try(:identifier), requests_params).call + if gitea_request[:status] == :success && local_requests.update_attributes(gpid: gitea_request["body"]["number"]) + local_requests.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") + end + end + end + end + end + +end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 3a68ff7b..704ffd9f 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -1,6 +1,7 @@ class SettingsController < ApplicationController def show @old_projects_url = nil + get_navbar get_add_menu get_common_menu get_personal_menu @@ -9,6 +10,14 @@ class SettingsController < ApplicationController end private + def get_navbar + @navbar = default_laboratory.navbar + if User.current.logged? + pernal_index = {"name"=>"个人主页", "link"=>get_site_url("link", "/current_user"), "hidden"=>false} + @navbar << pernal_index + end + end + def get_add_menu @add = [] Site.add.select(:id, :name, :url, :key).to_a.map(&:serializable_hash).each do |site| diff --git a/app/controllers/users/statistics_controller.rb b/app/controllers/users/statistics_controller.rb index dffd3f60..1948af9b 100644 --- a/app/controllers/users/statistics_controller.rb +++ b/app/controllers/users/statistics_controller.rb @@ -12,7 +12,7 @@ class Users::StatisticsController < Users::BaseController @commit_data = [] date_range.each do |date| @date_data << date.strftime("%Y.%m.%d") - @issue_data << observed_user.issues.where("DATE(created_on) = ?", date).size + @issue_data << observed_user.issues.issue_issue.where("DATE(created_on) = ?", date).size @pull_request_data << observed_user.pull_requests.where("DATE(created_at) = ?", date).size date_commit_data = commit_data.blank? ? nil : commit_data.select{|item| item["timestamp"] == date.to_time.to_i} @commit_data << (date_commit_data.blank? ? 0 : date_commit_data[0]["contributions"].to_i) diff --git a/app/forms/base_form.rb b/app/forms/base_form.rb index 598644fc..ca82fa4b 100644 --- a/app/forms/base_form.rb +++ b/app/forms/base_form.rb @@ -49,7 +49,7 @@ class BaseForm def check_verifi_code(verifi_code, code) code = strip(code) - # return if code == "123123" # TODO 万能验证码,用于测试 + return if code == "123123" && EduSetting.get("code_debug") # 万能验证码,用于测试 # TODO 万能验证码,用于测试 raise VerifiCodeError, "验证码已失效" if !verifi_code&.effective? raise VerifiCodeError, "验证码不正确" if verifi_code&.code != code end diff --git a/app/forms/projects/update_form.rb b/app/forms/projects/update_form.rb index 3048bc07..a351420b 100644 --- a/app/forms/projects/update_form.rb +++ b/app/forms/projects/update_form.rb @@ -1,5 +1,5 @@ class Projects::UpdateForm < BaseForm - attr_accessor :name, :description, :project_category_id, :project_language_id, :private, :identifier, :user_id, :project_identifier + attr_accessor :name, :description, :project_category_id, :project_language_id, :private, :identifier, :user_id, :project_identifier, :project_name validates :name, presence: true validates :name, length: { maximum: 50 } validates :description, length: { maximum: 200 } @@ -10,6 +10,7 @@ class Projects::UpdateForm < BaseForm check_project_language(project_language_id) check_repository_name(user_id, identifier) unless identifier.blank? || identifier == project_identifier + check_project_name(user_id, name) unless name.blank? || name == project_name end end diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index c6924185..f1556376 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -1,148 +1,170 @@ -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) - Rails.logger.info author_json['Email'] - 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) - return nil if str.blank? - begin - content = Base64.decode64(str).force_encoding('UTF-8') - - c_regex = /\!\[.*?\]\((.*?)\)/ - src_regex = /src=\"(.*?)\"/ - ss = content.to_s.scan(c_regex) - ss_src = content.to_s.scan(src_regex) - total_images = ss + ss_src - if total_images.length > 0 - total_images.each do |s| - 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 - # 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=#{r_content}&ref=#{ref}"].join - end - content = content.gsub(/#{r_content}/, new_r_content) - 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'] - readme_render_decode64_content(content, owner, repo, ref) - 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) + 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) + Rails.logger.info author_json['Email'] + 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 diff --git a/app/libs/trace.rb b/app/libs/trace.rb new file mode 100644 index 00000000..b1c3c476 --- /dev/null +++ b/app/libs/trace.rb @@ -0,0 +1,21 @@ +module Trace + class << self + def trace_config + trace_config = {} + + begin + config = Rails.application.config_for(:configuration).symbolize_keys! + trace_config = config[:trace].symbolize_keys! + raise 'trace config missing' if trace_config.blank? + rescue => exception + raise ex if Rails.env.production? + + puts %Q{\033[33m [warning] gitea config or configuration.yml missing, + please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m} + trace_config = {} + end + + trace_config + end + end +end \ No newline at end of file diff --git a/app/models/team.rb b/app/models/team.rb index c2e63338..b831cd06 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -29,6 +29,11 @@ class Team < ApplicationRecord has_many :team_units, dependent: :destroy has_many :team_users, dependent: :destroy + scope :like, lambda { |keywords| + sql = "teams.nickname LIKE :search OR teams.name LIKE :search" + where(sql, :search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? + } + validates :name, uniqueness: {scope: :organization_id} enum authorize: {read: 1, write: 2, admin: 3, owner: 4} diff --git a/app/models/trace_user.rb b/app/models/trace_user.rb new file mode 100644 index 00000000..e8964156 --- /dev/null +++ b/app/models/trace_user.rb @@ -0,0 +1,58 @@ +# == Schema Information +# +# Table name: trace_users +# +# id :integer not null, primary key +# user_id :integer +# username :string(255) +# password :string(255) +# unit :string(255) +# telnumber :string(255) +# email :string(255) +# name :string(255) +# token :text(65535) +# expired_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_trace_users_on_user_id (user_id) +# + +# 代码溯源 用户 +class TraceUser < ApplicationRecord + + belongs_to :user + + + def build_self_data + return if user.nil? + + self.username = user.login + self.password = SecureRandom.hex + self.unit = user.custom_department.blank? ? 'GitLink' : user.custom_department + self.telnumber = user.phone.blank? ? '13800000000' : user.phone + self.email = user.mail + self.name = user.nickname.blank? ? user.login : user.nickname + + self + end + + + def build_token + return if username.blank? || password.blank? || unit.blank? || telnumber.blank? || email.blank? || name.blank? + + response = Trace::AddUserService.call(username, password, unit, telnumber, email, name) + self.token = response[1]['token'] + self.expired_at = Time.now + 1.hours + end + + def refresh_token + return if username.blank? || password.blank? || unit.blank? || telnumber.blank? || email.blank? || name.blank? + + response = Trace::LoginService.call(username, password) + self.token = response[1]['token'] + self.expired_at = Time.now + 1.hours + end +end diff --git a/app/models/user.rb b/app/models/user.rb index c71a0249..909c75ad 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -174,6 +174,7 @@ class User < Owner has_many :system_notification_histories has_many :system_notifications, through: :system_notification_histories + has_one :trace_user, dependent: :destroy # Groups and active users scope :active, lambda { where(status: [STATUS_ACTIVE, STATUS_EDIT_INFO]) } @@ -789,6 +790,24 @@ class User < Owner self.nickname.present? && self.mail.present? end + def trace_token + if trace_user.present? + if trace_user.expired_at < Time.now + trace_user.refresh_token + trace_user.save + end + return trace_user.token + else + tu = TraceUser.new + tu.user = self + tu.build_self_data + tu.build_token + tu.save + + return tu.token + end + end + protected def validate_password_length # 管理员的初始密码是5位 diff --git a/app/services/issues/list_query_service.rb b/app/services/issues/list_query_service.rb index f85e2cb9..def9644d 100644 --- a/app/services/issues/list_query_service.rb +++ b/app/services/issues/list_query_service.rb @@ -27,12 +27,12 @@ class Issues::ListQueryService < ApplicationService issues = issues.joins(:pull_request).where(pull_requests: {status: 1}) end - if search_name.present? - issues = issues.where("subject LIKE ? OR description LIKE ? ", "%#{search_name}%", "%#{search_name}%") - end + # if search_name.present? + # issues = issues.where("issues.subject LIKE ? OR issues.description LIKE ? ", "%#{search_name}%", "%#{search_name}%") + # end if start_time&.present? || end_time&.present? - issues = issues.where("start_date between ? and ?",start_time&.present? ? start_time.to_date : Time.now.to_date, end_time&.present? ? end_time.to_date : Time.now.to_date) + issues = issues.where("issues.start_date between ? and ?",start_time&.present? ? start_time.to_date : Time.now.to_date, end_time&.present? ? end_time.to_date : Time.now.to_date) end issues = issues.where(author_id: params[:author_id]) if params[:author_id].present? && params[:author_id].to_s != "all" diff --git a/app/services/pull_requests/create_service.rb b/app/services/pull_requests/create_service.rb index d15b3f9f..703bb5de 100644 --- a/app/services/pull_requests/create_service.rb +++ b/app/services/pull_requests/create_service.rb @@ -132,8 +132,8 @@ class PullRequests::CreateService < ApplicationService end def merge_original_pull_params - base_pull_params[:head] = CGI.escape(base_pull_params[:head]) - base_pull_params[:base] = CGI.escape(base_pull_params[:base]) + base_pull_params[:head] = Addressable::URI.escape(base_pull_params[:head]) + base_pull_params[:base] = Addressable::URI.escape(base_pull_params[:base]) if pull_request.is_original && @params[:merge_user_login] base_pull_params.merge(head: "#{@params[:merge_user_login]}:#{base_pull_params[:head]}") else @@ -157,6 +157,7 @@ class PullRequests::CreateService < ApplicationService raise "head参数不能为空" if @params[:head].blank? raise "base参数不能为空" if @params[:base].blank? raise "fork_project_id参数错误" if is_original && !@project.forked_projects.pluck(:id).include?(@params[:fork_project_id].to_i) + raise "merge_user_login参数错误" if is_original && @project.fork_users.joins(:user).where(users: {login: @params[:merge_user_login]}).blank? raise "分支内容相同,无需创建合并请求" if @params[:head] === @params[:base] && !is_original raise "合并请求已存在" if @project&.pull_requests.where(head: @params[:head], base: @params[:base], status: 0, is_original: is_original, fork_project_id: @params[:fork_project_id]).present? raise @pull_issue.errors.full_messages.join(", ") unless pull_issue.valid? @@ -165,7 +166,7 @@ class PullRequests::CreateService < ApplicationService def compare_head_base! head = pull_request.is_original && @params[:merge_user_login] ? "#{@params[:merge_user_login]}/#{@project.identifier}:#{@params[:head]}" : @params[:head] - compare_result = Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, CGI.escape(@params[:base]), CGI.escape(head), @current_user.gitea_token) + compare_result = Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, Addressable::URI.escape(@params[:base]), Addressable::URI.escape(head), @current_user.gitea_token) raise '分支内容相同,无需创建合并请求' if compare_result["Commits"].blank? && compare_result["Diff"].blank? end diff --git a/app/services/trace/add_user_service.rb b/app/services/trace/add_user_service.rb new file mode 100644 index 00000000..0099f6e9 --- /dev/null +++ b/app/services/trace/add_user_service.rb @@ -0,0 +1,37 @@ +# 代码溯源 添加用户 +class Trace::AddUserService < Trace::ClientService + + # 用户名 密码 单位 手机号 邮箱 昵称 + attr_accessor :username, :password, :unit, :telnumber, :email, :name + + def initialize(username, password, unit, telnumber, email, name) + @username = username + @password = password + @unit = unit + @telnumber = telnumber + @email = email + @name = name + end + + def call + result = post(url, {data: request_params}) + response = render_response(result) + end + + private + + def request_params + { + username: username, + password: password, + unit: unit, + telnumber: telnumber, + email: email, + name: name + } + end + + def url + "/user/addccfuser".freeze + end +end \ No newline at end of file diff --git a/app/services/trace/check_result_service.rb b/app/services/trace/check_result_service.rb new file mode 100644 index 00000000..f1dd61ab --- /dev/null +++ b/app/services/trace/check_result_service.rb @@ -0,0 +1,33 @@ +# 代码溯源 查询检测结果 +class Trace::CheckResultService < Trace::ClientService + + attr_accessor :token, :project_name, :file_name, :page_num, :page_size + + def initialize(token, project_name=nil, file_name=nil, page_num=1, page_size=15) + @token = token + @project_name = project_name + @file_name = file_name + @page_num = page_num + @page_size = page_size + end + + def call + result = authed_get(token, url, request_params) + reponse = render_response(result) + end + + private + def request_params + { + product_name: project_name, + file_name: file_name, + pageNum: page_num, + pageSize: page_size, + } + end + + def url + "/user/checkresult".freeze + end +end + diff --git a/app/services/trace/check_service.rb b/app/services/trace/check_service.rb new file mode 100644 index 00000000..d31bbcf0 --- /dev/null +++ b/app/services/trace/check_service.rb @@ -0,0 +1,36 @@ +# 代码溯源 开始检测 +class Trace::CheckService < Trace::ClientService + + attr_accessor :token, :project, :if_branch, :branch_tag + + def initialize(token, project, if_branch, branch_tag) + @token = token + @project = project + @if_branch = if_branch + @branch_tag = branch_tag + end + + def call + result = 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) + { + product_name: project&.name, + product_type: project&.category&.name, + code_type: project&.language&.name, + product_desc: project&.description, + git_url: repo['clone_url'], + if_branch: if_branch, + branch_tag: branch_tag + } + end + + def url + "/user/check".freeze + end +end + diff --git a/app/services/trace/client_service.rb b/app/services/trace/client_service.rb new file mode 100644 index 00000000..df9a4321 --- /dev/null +++ b/app/services/trace/client_service.rb @@ -0,0 +1,143 @@ +class Trace::ClientService < ApplicationService + + def post(url, params={}) + puts "[trace][POST] request params: #{params}" + conn.post do |req| + req.url full_url(url) + req.body = params[:data].to_json + end + end + + def authed_post(token, url, params={}) + puts "[trace][POST] request params: #{params}" + puts "[trace][POST] request token: #{token}" + conn.post do |req| + req.url full_url(url) + req.body = params[:data].to_json + req.headers['Authorization'] = token + end + end + + def get(url, params={}) + puts "[trace][GET] request params: #{params}" + conn.get do |req| + req.url full_url(url, 'get') + params.each_pair do |key, value| + req.params["#{key}"] = value + end + end + end + + def authed_get(token, url, params={}) + puts "[trace][GET] request params: #{params}" + puts "[trace][GET] request token: #{token}" + conn.get do |req| + req.url full_url(url, 'get') + params.each_pair do |key, value| + req.params["#{key}"] = value + end + req.headers['Authorization'] = token + end + end + + def delete(url, params={}) + puts "[trace][DELETE] request params: #{params}" + conn.delete do |req| + req.url full_url(url) + req.body = params[:data].to_json + end + end + + def authed_delete(token, url, params={}) + puts "[trace][DELETE] request params: #{params}" + puts "[trace][DELETE] request token: #{token}" + conn.delete do |req| + req.url full_url(url) + req.body = params[:data].to_json + req.headers['Authorization'] = token + end + end + + def patch(url, params={}) + puts "[trace][PATCH] request params: #{params}" + conn.patch do |req| + req.url full_url(url) + req.body = params[:data].to_json + end + end + + def authed_patch(token, url, params={}) + puts "[trace][PATCH] request params: #{params}" + puts "[trace][PATCH] request token: #{token}" + conn.patch do |req| + req.url full_url(url) + req.body = params[:data].to_json + req.headers['Authorization'] = token + end + end + + def put(url, params={}) + puts "[trace][PUT] request params: #{params}" + conn.put do |req| + req.url full_url(url) + req.body = params[:data].to_json + end + end + + def authed_put(token, url, params={}) + puts "[trace][PUT] request params: #{params}" + puts "[trace][PUT] request token: #{token}" + conn.put do |req| + req.url full_url(url) + req.body = params[:data].to_json + req.headers['Authorization'] = token + end + end + + private + def conn + @client ||= begin + Faraday.new(url: domain) do |req| + req.request :url_encoded + req.headers['Content-Type'] = 'application/json' + req.adapter Faraday.default_adapter + end + end + + @client + end + + def base_url + Trace.trace_config[:base_url] + end + + def domain + Trace.trace_config[:domain] + end + + def api_url + [domain, base_url].join('') + end + + def full_url(api_rest, action='post') + url = [api_url, api_rest].join('').freeze + url = action === 'get' ? url : URI.escape(url) + url = URI.escape(url) unless url.ascii_only? + puts "[trace] request url: #{url}" + return url + end + + def log_error(status, body) + puts "[trace] status: #{status}" + puts "[trace] body: #{body}" + end + + def render_response(response) + status = response.status + body = JSON.parse(response&.body) + + log_error(status, body) + + return [body["code"], body["Data"], body["Error"]] + end +end \ No newline at end of file diff --git a/app/services/trace/login_service.rb b/app/services/trace/login_service.rb new file mode 100644 index 00000000..9f59d8e1 --- /dev/null +++ b/app/services/trace/login_service.rb @@ -0,0 +1,29 @@ +# 代码溯源 用户登录 +class Trace::LoginService < Trace::ClientService + + # 用户名 密码 + attr_accessor :username, :password + + def initialize(username, password) + @username = username + @password = password + end + + def call + result = post(url, {data: request_params}) + response = render_response(result) + end + + private + + def request_params + { + username: username, + password: password, + } + end + + def url + "/user/login".freeze + end +end \ No newline at end of file diff --git a/app/services/trace/pdf_report_service.rb b/app/services/trace/pdf_report_service.rb new file mode 100644 index 00000000..e91a78b3 --- /dev/null +++ b/app/services/trace/pdf_report_service.rb @@ -0,0 +1,26 @@ +# 代码溯源 导出pdf +class Trace::PdfReportService < Trace::ClientService + + attr_accessor :token, :task_id + + def initialize(token, task_id) + @token = token + @task_id = task_id + end + + def call + result = authed_get(token, url, request_params) + response = render_response(result) + end + + private + def request_params + { + task_id: task_id + } + end + + def url + "/user/pdfreport".freeze + end +end \ No newline at end of file diff --git a/app/services/trace/reload_check_service.rb b/app/services/trace/reload_check_service.rb new file mode 100644 index 00000000..62530af1 --- /dev/null +++ b/app/services/trace/reload_check_service.rb @@ -0,0 +1,25 @@ +# 代码溯源 重新检测 +class Trace::ReloadCheckService < Trace::ClientService + + attr_accessor :token, :fake_project_id + def initialize(token, fake_project_id) + @token = token + @fake_project_id = fake_project_id + end + + def call + result = authed_post(token, url, {data: request_params}) + response = render_response(result) + end + + private + def request_params + { + project_id: fake_project_id + } + end + + def url + '/user/reloadcheck'.freeze + end +end \ No newline at end of file diff --git a/app/views/organizations/organization_users/_detail.json.jbuilder b/app/views/organizations/organization_users/_detail.json.jbuilder index d4c21d5f..c7572971 100644 --- a/app/views/organizations/organization_users/_detail.json.jbuilder +++ b/app/views/organizations/organization_users/_detail.json.jbuilder @@ -3,5 +3,5 @@ json.user do json.partial! "organizations/user_detail", user: org_user.user end -json.team_names org_user.teams.pluck(:name) +json.team_names org_user.teams.pluck(:nickname) json.created_at org_user.created_at.strftime("%Y-%m-%d") diff --git a/app/views/repositories/_simple_entry.json.jbuilder b/app/views/repositories/_simple_entry.json.jbuilder index 9d0998c8..261eb4d2 100644 --- a/app/views/repositories/_simple_entry.json.jbuilder +++ b/app/views/repositories/_simple_entry.json.jbuilder @@ -1,4 +1,5 @@ if @project.forge? + is_dir = @sub_entries.is_a?(Array) file_name = entry['name'] file_type = File.extname(file_name.to_s)[1..-1] direct_download = download_type(file_type) @@ -9,16 +10,16 @@ if @project.forge? json.type entry['type'] json.size entry['size'] - json.content direct_download ? nil : decode64_content(entry, @owner, @repository, @ref, @path) + json.content (direct_download || image_type || is_dir) ? nil : decode64_content(entry, @owner, @repository, @ref, @path) json.target entry['target'] download_url = if image_type dir_path = [@owner.login, @repository.identifier, "raw/branch", @ref].join('/') - render_download_image_url(dir_path, entry['path'], decode64_content(entry, @owner, @repository, @ref)) + is_dir ? "" : render_download_image_url(dir_path, entry['path'], decode64_content(entry, @owner, @repository, @ref)) else # entry['download_url'] - render_download_file_url(@owner, @repository, entry['path'].to_s, @ref) + is_dir ? "" : render_download_file_url(@owner, @repository, entry['path'].to_s, @ref) end json.download_url download_url diff --git a/app/views/settings/show.json.jbuilder b/app/views/settings/show.json.jbuilder index 191f27bd..4c57d573 100644 --- a/app/views/settings/show.json.jbuilder +++ b/app/views/settings/show.json.jbuilder @@ -18,7 +18,7 @@ json.setting do # # end - nav_bar = default_setting.navbar + # nav_bar = default_setting.navbar # if User.current.logged? # nav_bar[2]["link"] = "https://forgeplus.trustie.net/users/#{current_user.login}/projects" @@ -39,7 +39,7 @@ json.setting do json.moop_cases_banner_url default_setting.moop_cases_banner_url&.[](1..-1) json.oj_banner_url default_setting.oj_banner_url&.[](1..-1) - json.navbar nav_bar + json.navbar @navbar json.footer default_setting.footer diff --git a/config/configuration.yml.example b/config/configuration.yml.example index c32366d9..f056dee1 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -67,6 +67,10 @@ default: &default write_domain: '' read_domain: '' base_url: '' + + trace: + domain: '' + base_url: '' forum: domain: '' diff --git a/config/routes.rb b/config/routes.rb index 28377f44..21caf682 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -692,7 +692,6 @@ Rails.application.routes.draw do resources :experience_forums resources :pinned_forums end - resources :project_statistics, only: [:index] do collection do get :visits_static diff --git a/db/migrate/20220331031530_create_trace_users.rb b/db/migrate/20220331031530_create_trace_users.rb new file mode 100644 index 00000000..5c8b532c --- /dev/null +++ b/db/migrate/20220331031530_create_trace_users.rb @@ -0,0 +1,17 @@ +class CreateTraceUsers < ActiveRecord::Migration[5.2] + def change + create_table :trace_users do |t| + t.references :user + t.string :username + t.string :password + t.string :unit + t.string :telnumber + t.string :email + t.string :name + t.text :token + t.datetime :expired_at + + t.timestamps + end + end +end diff --git a/public/assets/.sprockets-manifest-7657344e1d61e579de6a996a4498d7a2.json b/public/assets/.sprockets-manifest-7657344e1d61e579de6a996a4498d7a2.json index 1cdf5b08..4e20baac 100644 --- a/public/assets/.sprockets-manifest-7657344e1d61e579de6a996a4498d7a2.json +++ b/public/assets/.sprockets-manifest-7657344e1d61e579de6a996a4498d7a2.json @@ -1 +1 @@ -{"files":{"admin-1bd781fc5959b4f0e8879fa92afc5cec47e2d1a9b79cad9e55177fc1197a0e5b.js":{"logical_path":"admin.js","mtime":"2019-11-27T19:06:45+08:00","size":4594770,"digest":"1bd781fc5959b4f0e8879fa92afc5cec47e2d1a9b79cad9e55177fc1197a0e5b","integrity":"sha256-G9eB/FlZtPDoh5+pKvxc7Efi0am3nK2eVRd/wRl6Dls="},"admin-e78dd8b2041c26973b3851180e413539c07042575e336147194b5f2a1f7fa09c.css":{"logical_path":"admin.css","mtime":"2019-11-21T17:49:31+08:00","size":817848,"digest":"e78dd8b2041c26973b3851180e413539c07042575e336147194b5f2a1f7fa09c","integrity":"sha256-543YsgQcJpc7OFEYDkE1OcBwQldeM2FHGUtfKh9/oJw="},"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot":{"logical_path":"font-awesome/fontawesome-webfont.eot","mtime":"2021-06-03T10:59:24+08:00","size":165742,"digest":"7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979","integrity":"sha256-e/yrbbmdXPvxcFygU23ceFhUMsxfpBu9etDwCQM7KXk="},"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2":{"logical_path":"font-awesome/fontawesome-webfont.woff2","mtime":"2021-06-03T10:59:24+08:00","size":77160,"digest":"2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe","integrity":"sha256-Kt78vAQefRj88tQXh53FoJmXqmTWdbejxLbOM9oT8/4="},"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff":{"logical_path":"font-awesome/fontawesome-webfont.woff","mtime":"2021-06-03T10:59:24+08:00","size":98024,"digest":"ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07","integrity":"sha256-ugxZ3rVFD1y0Gz+TYJ7i0NmVQVh33foiPoqKdTNHTwc="},"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf":{"logical_path":"font-awesome/fontawesome-webfont.ttf","mtime":"2021-06-03T10:59:24+08:00","size":165548,"digest":"aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8","integrity":"sha256-qljzPyOaD7AvXHpsRcBD16msmgkzNYBmlOzW1O3A1qg="},"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg":{"logical_path":"font-awesome/fontawesome-webfont.svg","mtime":"2021-06-03T10:59:24+08:00","size":444379,"digest":"ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4","integrity":"sha256-rWFXkmwWIrpOHQPUePFUE2hSS/xG9R5C/g2UX37zI+Q="},"college-431d908264782ef54e90202095d4cf397c586f74d2b7879684348dc8b53d2cd2.js":{"logical_path":"college.js","mtime":"2020-01-07T14:59:22+08:00","size":3570046,"digest":"431d908264782ef54e90202095d4cf397c586f74d2b7879684348dc8b53d2cd2","integrity":"sha256-Qx2QgmR4LvVOkCAgldTPOXxYb3TSt4eWhDSNyLU9LNI="},"college-eb35b6573dea2a069abd5acb0211940c2165fa21da333555fa859ed155b3ca1f.css":{"logical_path":"college.css","mtime":"2019-11-20T17:50:44+08:00","size":565772,"digest":"eb35b6573dea2a069abd5acb0211940c2165fa21da333555fa859ed155b3ca1f","integrity":"sha256-6zW2Vz3qKgaavVrLAhGUDCFl+iHaMzVV+oWe0VWzyh8="},"cooperative-4f2218bb223392ea4332e9ace5a748baccd4fe66d4b5cc3b5574f97a425203ec.js":{"logical_path":"cooperative.js","mtime":"2020-01-07T14:59:22+08:00","size":4478060,"digest":"4f2218bb223392ea4332e9ace5a748baccd4fe66d4b5cc3b5574f97a425203ec","integrity":"sha256-TyIYuyIzkupDMums5adIuszU/mbUtcw7VXT5ekJSA+w="},"cooperative-9244063fa63cd29c9c3b074af565be75a130cfb31741b2f5252fe68a1f5c13c5.css":{"logical_path":"cooperative.css","mtime":"2019-11-20T17:50:44+08:00","size":799850,"digest":"9244063fa63cd29c9c3b074af565be75a130cfb31741b2f5252fe68a1f5c13c5","integrity":"sha256-kkQGP6Y80pycOwdK9WW+daEwz7MXQbL1JS/mih9cE8U="},"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png":{"logical_path":"logo.png","mtime":"2021-07-16T11:58:16+08:00","size":2816,"digest":"7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423","integrity":"sha256-f/ESVocJv5f5iY/ockm3qPIA/x9I1TfYWvhyFfGHBCM="},"application-d44f4301c7dfbe07bcb2788d7c006c22c184ae6b7016c09f7911b4962aacd767.js":{"logical_path":"application.js","mtime":"2020-01-07T14:59:22+08:00","size":615525,"digest":"d44f4301c7dfbe07bcb2788d7c006c22c184ae6b7016c09f7911b4962aacd767","integrity":"sha256-1E9DAcffvge8sniNfABsIsGErmtwFsCfeRG0liqs12c="},"application-2bf79ac2818959eb18d4df720a0cd0721b3b2385dd4565d635851fc41e192975.css":{"logical_path":"application.css","mtime":"2019-09-09T09:26:59+08:00","size":401033,"digest":"2bf79ac2818959eb18d4df720a0cd0721b3b2385dd4565d635851fc41e192975","integrity":"sha256-K/eawoGJWesY1N9yCgzQchs7I4XdRWXWNYUfxB4ZKXU="},"admin-83797d03d53c38db8e4751348516f8b7c55842cf8899066b86b3b2e6b6b1c8ad.js":{"logical_path":"admin.js","mtime":"2019-12-02T10:11:32+08:00","size":4583649,"digest":"83797d03d53c38db8e4751348516f8b7c55842cf8899066b86b3b2e6b6b1c8ad","integrity":"sha256-g3l9A9U8ONuOR1E0hRb4t8VYQs+ImQZrhrOy5raxyK0="},"admin-1512839e7eaa2136bdf9b238e3710004f8b90f2b74300c8ae7d9f9b25dddf077.css":{"logical_path":"admin.css","mtime":"2019-11-27T15:52:07+08:00","size":868584,"digest":"1512839e7eaa2136bdf9b238e3710004f8b90f2b74300c8ae7d9f9b25dddf077","integrity":"sha256-FRKDnn6qITa9+bI443EABPi5Dyt0MAyK59n5sl3d8Hc="},"college-1e70702e2d864fb4d5f57841bfa5937e31c7c059e6cd672a07f0b4b20740f607.js":{"logical_path":"college.js","mtime":"2019-11-20T16:00:49+08:00","size":3569292,"digest":"1e70702e2d864fb4d5f57841bfa5937e31c7c059e6cd672a07f0b4b20740f607","integrity":"sha256-HnBwLi2GT7TV9XhBv6WTfjHHwFnmzWcqB/C0sgdA9gc="},"college-a14be76ebc459e3bedd86e64c62b07c2dfc7ce632d73b86a7270b17462e5b746.css":{"logical_path":"college.css","mtime":"2019-11-11T18:25:42+08:00","size":610352,"digest":"a14be76ebc459e3bedd86e64c62b07c2dfc7ce632d73b86a7270b17462e5b746","integrity":"sha256-oUvnbrxFnjvt2G5kxisHwt/HzmMtc7hqcnCxdGLlt0Y="},"cooperative-bbf9b1ef14747d17410f2f38a6f308697335f86d4525ed6a5579905efc314ef3.js":{"logical_path":"cooperative.js","mtime":"2019-11-20T16:00:49+08:00","size":4463241,"digest":"bbf9b1ef14747d17410f2f38a6f308697335f86d4525ed6a5579905efc314ef3","integrity":"sha256-u/mx7xR0fRdBDy84pvMIaXM1+G1FJe1qVXmQXvwxTvM="},"cooperative-c36bba05d6a13482ccb6c3696ba5d750841dec9cae7a8043a0318c34c3a4638e.css":{"logical_path":"cooperative.css","mtime":"2019-11-17T09:36:46+08:00","size":849736,"digest":"c36bba05d6a13482ccb6c3696ba5d750841dec9cae7a8043a0318c34c3a4638e","integrity":"sha256-w2u6BdahNILMtsNpa6XXUIQd7JyueoBDoDGMNMOkY44="},"application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js":{"logical_path":"application.js","mtime":"2019-11-20T16:00:49+08:00","size":600706,"digest":"9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb","integrity":"sha256-nPvD15JZmh0N5ce4QgnhwrLmAzbw8B4Z8FgWY5GHCPs="},"application-8c9d6bb61c50908f584b3070c79aeb95f25c1166d39e07da5e95438b39ca0de9.css":{"logical_path":"application.css","mtime":"2019-10-21T22:52:15+08:00","size":436995,"digest":"8c9d6bb61c50908f584b3070c79aeb95f25c1166d39e07da5e95438b39ca0de9","integrity":"sha256-jJ1rthxQkI9YSzBwx5rrlfJcEWbTngfaXpVDiznKDek="},"admin-f1a4d30772fa41e1edd288c1b1d39deeac7481d590b13a886953adf2f2e4f47c.js":{"logical_path":"admin.js","mtime":"2019-11-29T15:05:39+08:00","size":4583647,"digest":"f1a4d30772fa41e1edd288c1b1d39deeac7481d590b13a886953adf2f2e4f47c","integrity":"sha256-8aTTB3L6QeHt0ojBsdOd7qx0gdWQsTqIaVOt8vLk9Hw="},"admin-0172e0b18d559d9648df58d7954e4843256ba2d2eda03543b7cc6c955c8e2eb3.js":{"logical_path":"admin.js","mtime":"2019-12-02T10:58:14+08:00","size":4583648,"digest":"0172e0b18d559d9648df58d7954e4843256ba2d2eda03543b7cc6c955c8e2eb3","integrity":"sha256-AXLgsY1VnZZI31jXlU5IQyVrotLtoDVDt8xslVyOLrM="},"admin-6da32058962cbfc9483b468c26d5f9b968b5f970a2031f73952863c554cd6254.js":{"logical_path":"admin.js","mtime":"2019-12-02T17:44:43+08:00","size":4583680,"digest":"6da32058962cbfc9483b468c26d5f9b968b5f970a2031f73952863c554cd6254","integrity":"sha256-baMgWJYsv8lIO0aMJtX5uWi1+XCiAx9zlShjxVTNYlQ="},"admin-64b90a423722da851a01011acac435b5f29f25e3b741e78854ea1a40742d89f7.js":{"logical_path":"admin.js","mtime":"2019-12-06T11:19:59+08:00","size":4584886,"digest":"64b90a423722da851a01011acac435b5f29f25e3b741e78854ea1a40742d89f7","integrity":"sha256-ZLkKQjci2oUaAQEaysQ1tfKfJeO3QeeIVOoaQHQtifc="},"admin-38dd06255909789c15bb1ae3d2c6b25064dd59d463ed996af2cd93b7aa08626b.css":{"logical_path":"admin.css","mtime":"2019-12-06T11:00:53+08:00","size":870104,"digest":"38dd06255909789c15bb1ae3d2c6b25064dd59d463ed996af2cd93b7aa08626b","integrity":"sha256-ON0GJVkJeJwVuxrj0sayUGTdWdRj7Zlq8s2Tt6oIYms="},"admin-f1565aa714ea18c95e226d27a3685c4b81544a9b531f4d14f88f6aa43cc9f2fa.css":{"logical_path":"admin.css","mtime":"2019-12-06T11:33:41+08:00","size":870109,"digest":"f1565aa714ea18c95e226d27a3685c4b81544a9b531f4d14f88f6aa43cc9f2fa","integrity":"sha256-8VZapxTqGMleIm0no2hcS4FUSptTH00U+I9qpDzJ8vo="},"admin-7e5aa975448fc470736f18156994f5b445b131ed1dbce9fad3b2d1415a0947d6.js":{"logical_path":"admin.js","mtime":"2019-12-06T15:35:42+08:00","size":4584963,"digest":"7e5aa975448fc470736f18156994f5b445b131ed1dbce9fad3b2d1415a0947d6","integrity":"sha256-flqpdUSPxHBzbxgVaZT1tEWxMe0dvOn607LRQVoJR9Y="},"admin-7cf88529e1f8fdafe22b3aef833fbba4898c4a69fdc4043dc9f1796c6f100ead.css":{"logical_path":"admin.css","mtime":"2019-12-06T18:03:36+08:00","size":870265,"digest":"7cf88529e1f8fdafe22b3aef833fbba4898c4a69fdc4043dc9f1796c6f100ead","integrity":"sha256-fPiFKeH4/a/iKzrvgz+7pImMSmn9xAQ9yfF5bG8QDq0="},"admin-aaff9ffea9f10689b47c00147b5cc6650961733dbc8ca30d23eb9afc2e6ee3dd.js":{"logical_path":"admin.js","mtime":"2019-12-06T18:20:13+08:00","size":4584962,"digest":"aaff9ffea9f10689b47c00147b5cc6650961733dbc8ca30d23eb9afc2e6ee3dd","integrity":"sha256-qv+f/qnxBom0fAAUe1zGZQlhcz28jKMNI+ua/C5u490="},"admin-ab3a763c3d168f05c11ca0a9f0cadc3030dc9f0ae0317085d9032d47643f7488.js":{"logical_path":"admin.js","mtime":"2019-12-26T16:08:18+08:00","size":4607541,"digest":"ab3a763c3d168f05c11ca0a9f0cadc3030dc9f0ae0317085d9032d47643f7488","integrity":"sha256-qzp2PD0WjwXBHKCp8MrcMDDcnwrgMXCF2QMtR2Q/dIg="},"admin-31e7cc6208d66802217e899555cd22920bb7d043578b1b16250e09dbe76b7bc6.css":{"logical_path":"admin.css","mtime":"2019-12-10T16:46:58+08:00","size":843730,"digest":"31e7cc6208d66802217e899555cd22920bb7d043578b1b16250e09dbe76b7bc6","integrity":"sha256-MefMYgjWaAIhfomVVc0ikgu30ENXixsWJQ4J2+dre8Y="},"college-893ba916d2b043f4b751cacc104de43d14e0db0b4001822f996e803bacbda169.css":{"logical_path":"college.css","mtime":"2019-12-10T16:46:58+08:00","size":589973,"digest":"893ba916d2b043f4b751cacc104de43d14e0db0b4001822f996e803bacbda169","integrity":"sha256-iTupFtKwQ/S3UcrMEE3kPRTg2wtAAYIvmW6AO6y9oWk="},"cooperative-bef1905d0c3e15357e3a376225923847b33f7388d49741062fe3cb20346da315.css":{"logical_path":"cooperative.css","mtime":"2019-12-10T16:46:58+08:00","size":824051,"digest":"bef1905d0c3e15357e3a376225923847b33f7388d49741062fe3cb20346da315","integrity":"sha256-vvGQXQw+FTV+OjdiJZI4R7M/c4jUl0EGL+PLIDRtoxU="},"application-8853af8ba870ca7fc8bdae048322f1c5a5caa26f1df87dbbf0c6d04232c4ac4e.css":{"logical_path":"application.css","mtime":"2019-10-21T22:52:15+08:00","size":418844,"digest":"8853af8ba870ca7fc8bdae048322f1c5a5caa26f1df87dbbf0c6d04232c4ac4e","integrity":"sha256-iFOvi6hwyn/Iva4EgyLxxaXKom8d+H278MbQQjLErE4="},"admin-f16eb906c3b6dce2c588677637473a128cd7b3d56c8d15b5eba621e9df586f37.js":{"logical_path":"admin.js","mtime":"2020-01-03T16:30:22+08:00","size":4610769,"digest":"f16eb906c3b6dce2c588677637473a128cd7b3d56c8d15b5eba621e9df586f37","integrity":"sha256-8W65BsO23OLFiGd2N0c6EozXs9VsjRW166Yh6d9Ybzc="},"admin-bed977ce14a5b8a1dcb718a4faa7971f54aad01ed4fa9b01a3d2ee333ed36cf8.js":{"logical_path":"admin.js","mtime":"2020-01-03T17:41:50+08:00","size":4611577,"digest":"bed977ce14a5b8a1dcb718a4faa7971f54aad01ed4fa9b01a3d2ee333ed36cf8","integrity":"sha256-vtl3zhSluKHctxik+qeXH1Sq0B7U+psBo9LuMz7TbPg="},"admin-d10431d175587afdb06f99d563f03b9890002351aa3b9a5e568d0396fd5cfe22.js":{"logical_path":"admin.js","mtime":"2020-01-31T16:18:47+08:00","size":4613282,"digest":"d10431d175587afdb06f99d563f03b9890002351aa3b9a5e568d0396fd5cfe22","integrity":"sha256-0QQx0XVYev2wb5nVY/A7mJAAI1GqO5peVo0Dlv1c/iI="},"admin-809fe775cb1fb357743738f65e0761be6b9fbe09dcd7062559f22dd87f1e059c.js":{"logical_path":"admin.js","mtime":"2020-02-04T19:26:18+08:00","size":4621538,"digest":"809fe775cb1fb357743738f65e0761be6b9fbe09dcd7062559f22dd87f1e059c","integrity":"sha256-gJ/ndcsfs1d0Nzj2Xgdhvmufvgnc1wYlWfIt2H8eBZw="},"admin-15ad6e029d1195111f661b61d8252ec40b1e453c44c489c661c8fa3211ff62b2.css":{"logical_path":"admin.css","mtime":"2020-02-05T14:22:08+08:00","size":846651,"digest":"15ad6e029d1195111f661b61d8252ec40b1e453c44c489c661c8fa3211ff62b2","integrity":"sha256-Fa1uAp0RlREfZhth2CUuxAseRTxExInGYcj6MhH/YrI="},"admin-c4d3fa2b049fa3abe12d80939ccd28ab8a824b1469cdb2c72f7daf1483ab6da1.js":{"logical_path":"admin.js","mtime":"2020-02-05T11:49:01+08:00","size":4622038,"digest":"c4d3fa2b049fa3abe12d80939ccd28ab8a824b1469cdb2c72f7daf1483ab6da1","integrity":"sha256-xNP6KwSfo6vhLYCTnM0oq4qCSxRpzbLHL32vFIOrbaE="},"admin-d4133a4bdcbfd4ca5d2dd49735008d8db50f25757650f0b13a62e254dfb201b4.js":{"logical_path":"admin.js","mtime":"2020-02-05T16:36:51+08:00","size":4624767,"digest":"d4133a4bdcbfd4ca5d2dd49735008d8db50f25757650f0b13a62e254dfb201b4","integrity":"sha256-1BM6S9y/1MpdLdSXNQCNjbUPJXV2UPCxOmLiVN+yAbQ="},"admin-7c523d78203d02769553abade33413cdf663eea3fe1d15ee07ab449240a10a9a.js":{"logical_path":"admin.js","mtime":"2020-02-19T15:51:39+08:00","size":4570082,"digest":"7c523d78203d02769553abade33413cdf663eea3fe1d15ee07ab449240a10a9a","integrity":"sha256-fFI9eCA9AnaVU6ut4zQTzfZj7qP+HRXuB6tEkkChCpo="},"college-f9c1b76699b39750bee976e1dbb5d832fdce2fb05edaf1b818c6e76fdf5ba77c.js":{"logical_path":"college.js","mtime":"2020-02-07T16:06:25+08:00","size":3505839,"digest":"f9c1b76699b39750bee976e1dbb5d832fdce2fb05edaf1b818c6e76fdf5ba77c","integrity":"sha256-+cG3Zpmzl1C+6Xbh27XYMv3OL7Be2vG4GMbnb99bp3w="},"college-2065ab3f84df62eb2ca345c83198920fad15cc3b93baf22c091f3c6bfced11b4.css":{"logical_path":"college.css","mtime":"2020-02-05T14:22:08+08:00","size":586787,"digest":"2065ab3f84df62eb2ca345c83198920fad15cc3b93baf22c091f3c6bfced11b4","integrity":"sha256-IGWrP4TfYusso0XIMZiSD60VzDuTuvIsCR88a/ztEbQ="},"cooperative-2efdf753e39599e78fb02527e864784df9a34dd971576db8231b63f618f9786e.js":{"logical_path":"cooperative.js","mtime":"2020-02-10T22:03:17+08:00","size":4413858,"digest":"2efdf753e39599e78fb02527e864784df9a34dd971576db8231b63f618f9786e","integrity":"sha256-Lv33U+OVmeePsCUn6GR4TfmjTdlxV224Ixtj9hj5eG4="},"cooperative-20747152aeb5e0bca475a49899a771a58d73be01d62c8f39396540c4f0abb10c.css":{"logical_path":"cooperative.css","mtime":"2020-02-05T14:22:08+08:00","size":826122,"digest":"20747152aeb5e0bca475a49899a771a58d73be01d62c8f39396540c4f0abb10c","integrity":"sha256-IHRxUq614LykdaSYmadxpY1zvgHWLI85OWVAxPCrsQw="},"application-47eaf72a32e7cee15e04ee7632fbbaadd5efd1129bfe0978d28d9b84ecec913e.js":{"logical_path":"application.js","mtime":"2020-02-07T16:06:25+08:00","size":551318,"digest":"47eaf72a32e7cee15e04ee7632fbbaadd5efd1129bfe0978d28d9b84ecec913e","integrity":"sha256-R+r3KjLnzuFeBO52Mvu6rdXv0RKb/gl40o2bhOzskT4="},"application-d3da385d89dfa7edfbecc09573322b132ffc5580cbd185d5f7a74d5358881815.css":{"logical_path":"application.css","mtime":"2019-09-09T09:26:59+08:00","size":419785,"digest":"d3da385d89dfa7edfbecc09573322b132ffc5580cbd185d5f7a74d5358881815","integrity":"sha256-09o4XYnfp+377MCVczIrEy/8VYDL0YXV96dNU1iIGBU="},"admin-8d7c2001aaf2599bddad06c5bc30526b059780d9fda2f01797524c0b8a1c43c3.js":{"logical_path":"admin.js","mtime":"2020-02-22T18:21:49+08:00","size":4636232,"digest":"8d7c2001aaf2599bddad06c5bc30526b059780d9fda2f01797524c0b8a1c43c3","integrity":"sha256-jXwgAaryWZvdrQbFvDBSawWXgNn9ovAXl1JMC4ocQ8M="},"admin-343a62865a99bebf4247a945a48400b472f76196fb80e86b4cce6b2cc480d306.css":{"logical_path":"admin.css","mtime":"2019-12-23T10:10:47+08:00","size":838584,"digest":"343a62865a99bebf4247a945a48400b472f76196fb80e86b4cce6b2cc480d306","integrity":"sha256-NDpihlqZvr9CR6lFpIQAtHL3YZb7gOhrTM5rLMSA0wY="},"college-461b4e52a77522c5ff4341992359aba660dc26d7d985e394b69d8b87db954a14.css":{"logical_path":"college.css","mtime":"2019-11-19T17:05:09+08:00","size":578720,"digest":"461b4e52a77522c5ff4341992359aba660dc26d7d985e394b69d8b87db954a14","integrity":"sha256-RhtOUqd1IsX/Q0GZI1mrpmDcJtfZheOUtp2Lh9uVShQ="},"cooperative-286bab0b704bc8d5295bee551a48f78e1fe8713e0e50c65059cf7a65ed09bbf0.js":{"logical_path":"cooperative.js","mtime":"2020-02-09T13:28:20+08:00","size":4478065,"digest":"286bab0b704bc8d5295bee551a48f78e1fe8713e0e50c65059cf7a65ed09bbf0","integrity":"sha256-KGurC3BLyNUpW+5VGkj3jh/ocT4OUMZQWc96Ze0Ju/A="},"cooperative-716b37420680ce5fb3bedf40508ea89e07f4d7060b412b832c8b4e9b09ac024d.css":{"logical_path":"cooperative.css","mtime":"2019-11-19T17:05:09+08:00","size":818055,"digest":"716b37420680ce5fb3bedf40508ea89e07f4d7060b412b832c8b4e9b09ac024d","integrity":"sha256-cWs3QgaAzl+zvt9AUI6ongf01wYLQSuDLItOmwmsAk0="},"application-e194b45894d0c5240d954851211a86516ee6ad871ca99bf7783ce44aeb8c0687.css":{"logical_path":"application.css","mtime":"2019-11-19T17:04:45+08:00","size":413848,"digest":"e194b45894d0c5240d954851211a86516ee6ad871ca99bf7783ce44aeb8c0687","integrity":"sha256-4ZS0WJTQxSQNlUhRIRqGUW7mrYccqZv3eDzkSuuMBoc="},"admin-04ff7d4c76f406e0df9e02923038d175f26ee098d43e266c2138b07163a2da9e.js":{"logical_path":"admin.js","mtime":"2020-02-25T12:09:35+08:00","size":4635520,"digest":"04ff7d4c76f406e0df9e02923038d175f26ee098d43e266c2138b07163a2da9e","integrity":"sha256-BP99THb0BuDfngKSMDjRdfJu4JjUPiZsITiwcWOi2p4="},"admin-46d2460ea8c06a885f92df73240e8eb84d320402bbdf580ffe3e038d07fdda3d.js":{"logical_path":"admin.js","mtime":"2020-02-25T13:17:26+08:00","size":4635586,"digest":"46d2460ea8c06a885f92df73240e8eb84d320402bbdf580ffe3e038d07fdda3d","integrity":"sha256-RtJGDqjAaohfkt9zJA6OuE0yBAK731gP/j4DjQf92j0="},"admin-b8f26db185886f99021da91f0e2c1a8cc4ced4d1b895794751d11b7a9f45de98.js":{"logical_path":"admin.js","mtime":"2020-02-25T17:16:13+08:00","size":4637067,"digest":"b8f26db185886f99021da91f0e2c1a8cc4ced4d1b895794751d11b7a9f45de98","integrity":"sha256-uPJtsYWIb5kCHakfDiwajMTO1NG4lXlHUdEbep9F3pg="},"admin-8cb0e9724b33e253621a5f1020bdc270f828cb7d602de8434d3cc606e0b4dbc2.js":{"logical_path":"admin.js","mtime":"2020-02-25T17:51:23+08:00","size":4637275,"digest":"8cb0e9724b33e253621a5f1020bdc270f828cb7d602de8434d3cc606e0b4dbc2","integrity":"sha256-jLDpcksz4lNiGl8QIL3CcPgoy31gLehDTTzGBuC028I="},"admin-b2526c7eefd8212cde88c53131e8a7dd02f4d96e6d9e262d31dbca516addc979.js":{"logical_path":"admin.js","mtime":"2021-08-31T14:49:02+08:00","size":1600367,"digest":"b2526c7eefd8212cde88c53131e8a7dd02f4d96e6d9e262d31dbca516addc979","integrity":"sha256-slJsfu/YISzeiMUxMein3QL02W5tniYtMdvKUWrdyXk="},"admin-719988f975840fa3b187ef2594aecb8494218f82342377a1f3c71731f68fbf61.css":{"logical_path":"admin.css","mtime":"2021-08-06T09:41:07+08:00","size":366126,"digest":"719988f975840fa3b187ef2594aecb8494218f82342377a1f3c71731f68fbf61","integrity":"sha256-cZmI+XWED6Oxh+8llK7LhJQhj4I0I3eh88cXMfaPv2E="},"college-bd7fb1ab1da64733a0172f89c8b9d5cfe4db736bb3ee51e0ca0701b40e2e49cc.js":{"logical_path":"college.js","mtime":"2021-08-06T09:41:07+08:00","size":1090655,"digest":"bd7fb1ab1da64733a0172f89c8b9d5cfe4db736bb3ee51e0ca0701b40e2e49cc","integrity":"sha256-vX+xqx2mRzOgFy+JyLnVz+Tbc2uz7lHgygcBtA4uScw="},"college-fe5b44c2d2fe8fcd24143a2cb9df8254be9104d3fe4e3da0f3e3317838a16ebc.css":{"logical_path":"college.css","mtime":"2021-07-16T11:58:16+08:00","size":218568,"digest":"fe5b44c2d2fe8fcd24143a2cb9df8254be9104d3fe4e3da0f3e3317838a16ebc","integrity":"sha256-/ltEwtL+j80kFDosud+CVL6RBNP+Tj2g8+MxeDihbrw="},"cooperative-1f8e268f2c629e3453e71d3ad4bc2c043e73989a8804598ad807c4050ee3c346.js":{"logical_path":"cooperative.js","mtime":"2021-08-06T09:41:07+08:00","size":1495462,"digest":"1f8e268f2c629e3453e71d3ad4bc2c043e73989a8804598ad807c4050ee3c346","integrity":"sha256-H44mjyxinjRT5x061LwsBD5zmJqIBFmK2AfEBQ7jw0Y="},"cooperative-cf4adc259ee46508f0fba0ccaa4f2c3beb33dbb715cc354ec30fbb7db729ca77.css":{"logical_path":"cooperative.css","mtime":"2021-07-16T11:58:17+08:00","size":354672,"digest":"cf4adc259ee46508f0fba0ccaa4f2c3beb33dbb715cc354ec30fbb7db729ca77","integrity":"sha256-z0rcJZ7kZQjw+6DMqk8sO+sz27cVzDVOww+7fbcpync="},"oauth-79ad9eb7fd1c8cc81aed569f2c933d0bbd5a823dc450a1038c034eef2a7b6b9c.css":{"logical_path":"oauth.css","mtime":"2021-07-16T11:58:16+08:00","size":1497,"digest":"79ad9eb7fd1c8cc81aed569f2c933d0bbd5a823dc450a1038c034eef2a7b6b9c","integrity":"sha256-ea2et/0cjMga7VafLJM9C71agj3EUKEDjANO7yp7a5w="},"application-860dba70259cdc6c5c72c499f8be717c050d517f1fd6a1543c0b6863af987000.js":{"logical_path":"application.js","mtime":"2021-08-06T09:41:07+08:00","size":506517,"digest":"860dba70259cdc6c5c72c499f8be717c050d517f1fd6a1543c0b6863af987000","integrity":"sha256-hg26cCWc3GxccsSZ+L5xfAUNUX8f1qFUPAtoY6+YcAA="},"application-346b678500708f3f4e9cd72c46d1e9e54aaf76e0478b766519ac083bf93fbcff.css":{"logical_path":"application.css","mtime":"2021-07-16T11:58:16+08:00","size":159095,"digest":"346b678500708f3f4e9cd72c46d1e9e54aaf76e0478b766519ac083bf93fbcff","integrity":"sha256-NGtnhQBwjz9OnNcsRtHp5UqvduBHi3ZlGawIO/k/vP8="},"admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js":{"logical_path":"admin.js","mtime":"2021-12-01T16:23:57+08:00","size":1604075,"digest":"445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa","integrity":"sha256-RF8txfkgSPe83OyUvEp/y9XQd+eeW2SMjXQuBTHMb/o="},"admin-57e255d0e8a864feec96731ff46d815760e8b04da69678556992d6509b9575f0.css":{"logical_path":"admin.css","mtime":"2021-12-01T16:20:23+08:00","size":368094,"digest":"57e255d0e8a864feec96731ff46d815760e8b04da69678556992d6509b9575f0","integrity":"sha256-V+JV0OioZP7slnMf9G2BV2DosE2mlnhVaZLWUJuVdfA="},"logo-9d97159b1c6619af9d667616b4da0ef43201f45d82b3f591c93fcd5f233da2c9.png":{"logical_path":"logo.png","mtime":"2021-12-01T16:23:57+08:00","size":10051,"digest":"9d97159b1c6619af9d667616b4da0ef43201f45d82b3f591c93fcd5f233da2c9","integrity":"sha256-nZcVmxxmGa+dZnYWtNoO9DIB9F2Cs/WRyT/NXyM9osk="}},"assets":{"admin.js":"admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js","admin.css":"admin-57e255d0e8a864feec96731ff46d815760e8b04da69678556992d6509b9575f0.css","font-awesome/fontawesome-webfont.eot":"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot","font-awesome/fontawesome-webfont.woff2":"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2","font-awesome/fontawesome-webfont.woff":"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff","font-awesome/fontawesome-webfont.ttf":"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf","font-awesome/fontawesome-webfont.svg":"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg","college.js":"college-bd7fb1ab1da64733a0172f89c8b9d5cfe4db736bb3ee51e0ca0701b40e2e49cc.js","college.css":"college-fe5b44c2d2fe8fcd24143a2cb9df8254be9104d3fe4e3da0f3e3317838a16ebc.css","cooperative.js":"cooperative-1f8e268f2c629e3453e71d3ad4bc2c043e73989a8804598ad807c4050ee3c346.js","cooperative.css":"cooperative-cf4adc259ee46508f0fba0ccaa4f2c3beb33dbb715cc354ec30fbb7db729ca77.css","logo.png":"logo-9d97159b1c6619af9d667616b4da0ef43201f45d82b3f591c93fcd5f233da2c9.png","application.js":"application-860dba70259cdc6c5c72c499f8be717c050d517f1fd6a1543c0b6863af987000.js","application.css":"application-346b678500708f3f4e9cd72c46d1e9e54aaf76e0478b766519ac083bf93fbcff.css","oauth.css":"oauth-79ad9eb7fd1c8cc81aed569f2c933d0bbd5a823dc450a1038c034eef2a7b6b9c.css"}} \ No newline at end of file +{"files":{"admin-1bd781fc5959b4f0e8879fa92afc5cec47e2d1a9b79cad9e55177fc1197a0e5b.js":{"logical_path":"admin.js","mtime":"2019-11-27T19:06:45+08:00","size":4594770,"digest":"1bd781fc5959b4f0e8879fa92afc5cec47e2d1a9b79cad9e55177fc1197a0e5b","integrity":"sha256-G9eB/FlZtPDoh5+pKvxc7Efi0am3nK2eVRd/wRl6Dls="},"admin-e78dd8b2041c26973b3851180e413539c07042575e336147194b5f2a1f7fa09c.css":{"logical_path":"admin.css","mtime":"2019-11-21T17:49:31+08:00","size":817848,"digest":"e78dd8b2041c26973b3851180e413539c07042575e336147194b5f2a1f7fa09c","integrity":"sha256-543YsgQcJpc7OFEYDkE1OcBwQldeM2FHGUtfKh9/oJw="},"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot":{"logical_path":"font-awesome/fontawesome-webfont.eot","mtime":"2021-06-03T10:59:24+08:00","size":165742,"digest":"7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979","integrity":"sha256-e/yrbbmdXPvxcFygU23ceFhUMsxfpBu9etDwCQM7KXk="},"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2":{"logical_path":"font-awesome/fontawesome-webfont.woff2","mtime":"2021-06-03T10:59:24+08:00","size":77160,"digest":"2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe","integrity":"sha256-Kt78vAQefRj88tQXh53FoJmXqmTWdbejxLbOM9oT8/4="},"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff":{"logical_path":"font-awesome/fontawesome-webfont.woff","mtime":"2021-06-03T10:59:24+08:00","size":98024,"digest":"ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07","integrity":"sha256-ugxZ3rVFD1y0Gz+TYJ7i0NmVQVh33foiPoqKdTNHTwc="},"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf":{"logical_path":"font-awesome/fontawesome-webfont.ttf","mtime":"2021-06-03T10:59:24+08:00","size":165548,"digest":"aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8","integrity":"sha256-qljzPyOaD7AvXHpsRcBD16msmgkzNYBmlOzW1O3A1qg="},"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg":{"logical_path":"font-awesome/fontawesome-webfont.svg","mtime":"2021-06-03T10:59:24+08:00","size":444379,"digest":"ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4","integrity":"sha256-rWFXkmwWIrpOHQPUePFUE2hSS/xG9R5C/g2UX37zI+Q="},"college-431d908264782ef54e90202095d4cf397c586f74d2b7879684348dc8b53d2cd2.js":{"logical_path":"college.js","mtime":"2020-01-07T14:59:22+08:00","size":3570046,"digest":"431d908264782ef54e90202095d4cf397c586f74d2b7879684348dc8b53d2cd2","integrity":"sha256-Qx2QgmR4LvVOkCAgldTPOXxYb3TSt4eWhDSNyLU9LNI="},"college-eb35b6573dea2a069abd5acb0211940c2165fa21da333555fa859ed155b3ca1f.css":{"logical_path":"college.css","mtime":"2019-11-20T17:50:44+08:00","size":565772,"digest":"eb35b6573dea2a069abd5acb0211940c2165fa21da333555fa859ed155b3ca1f","integrity":"sha256-6zW2Vz3qKgaavVrLAhGUDCFl+iHaMzVV+oWe0VWzyh8="},"cooperative-4f2218bb223392ea4332e9ace5a748baccd4fe66d4b5cc3b5574f97a425203ec.js":{"logical_path":"cooperative.js","mtime":"2020-01-07T14:59:22+08:00","size":4478060,"digest":"4f2218bb223392ea4332e9ace5a748baccd4fe66d4b5cc3b5574f97a425203ec","integrity":"sha256-TyIYuyIzkupDMums5adIuszU/mbUtcw7VXT5ekJSA+w="},"cooperative-9244063fa63cd29c9c3b074af565be75a130cfb31741b2f5252fe68a1f5c13c5.css":{"logical_path":"cooperative.css","mtime":"2019-11-20T17:50:44+08:00","size":799850,"digest":"9244063fa63cd29c9c3b074af565be75a130cfb31741b2f5252fe68a1f5c13c5","integrity":"sha256-kkQGP6Y80pycOwdK9WW+daEwz7MXQbL1JS/mih9cE8U="},"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png":{"logical_path":"logo.png","mtime":"2021-07-16T11:58:16+08:00","size":2816,"digest":"7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423","integrity":"sha256-f/ESVocJv5f5iY/ockm3qPIA/x9I1TfYWvhyFfGHBCM="},"application-d44f4301c7dfbe07bcb2788d7c006c22c184ae6b7016c09f7911b4962aacd767.js":{"logical_path":"application.js","mtime":"2020-01-07T14:59:22+08:00","size":615525,"digest":"d44f4301c7dfbe07bcb2788d7c006c22c184ae6b7016c09f7911b4962aacd767","integrity":"sha256-1E9DAcffvge8sniNfABsIsGErmtwFsCfeRG0liqs12c="},"application-2bf79ac2818959eb18d4df720a0cd0721b3b2385dd4565d635851fc41e192975.css":{"logical_path":"application.css","mtime":"2019-09-09T09:26:59+08:00","size":401033,"digest":"2bf79ac2818959eb18d4df720a0cd0721b3b2385dd4565d635851fc41e192975","integrity":"sha256-K/eawoGJWesY1N9yCgzQchs7I4XdRWXWNYUfxB4ZKXU="},"admin-83797d03d53c38db8e4751348516f8b7c55842cf8899066b86b3b2e6b6b1c8ad.js":{"logical_path":"admin.js","mtime":"2019-12-02T10:11:32+08:00","size":4583649,"digest":"83797d03d53c38db8e4751348516f8b7c55842cf8899066b86b3b2e6b6b1c8ad","integrity":"sha256-g3l9A9U8ONuOR1E0hRb4t8VYQs+ImQZrhrOy5raxyK0="},"admin-1512839e7eaa2136bdf9b238e3710004f8b90f2b74300c8ae7d9f9b25dddf077.css":{"logical_path":"admin.css","mtime":"2019-11-27T15:52:07+08:00","size":868584,"digest":"1512839e7eaa2136bdf9b238e3710004f8b90f2b74300c8ae7d9f9b25dddf077","integrity":"sha256-FRKDnn6qITa9+bI443EABPi5Dyt0MAyK59n5sl3d8Hc="},"college-1e70702e2d864fb4d5f57841bfa5937e31c7c059e6cd672a07f0b4b20740f607.js":{"logical_path":"college.js","mtime":"2019-11-20T16:00:49+08:00","size":3569292,"digest":"1e70702e2d864fb4d5f57841bfa5937e31c7c059e6cd672a07f0b4b20740f607","integrity":"sha256-HnBwLi2GT7TV9XhBv6WTfjHHwFnmzWcqB/C0sgdA9gc="},"college-a14be76ebc459e3bedd86e64c62b07c2dfc7ce632d73b86a7270b17462e5b746.css":{"logical_path":"college.css","mtime":"2019-11-11T18:25:42+08:00","size":610352,"digest":"a14be76ebc459e3bedd86e64c62b07c2dfc7ce632d73b86a7270b17462e5b746","integrity":"sha256-oUvnbrxFnjvt2G5kxisHwt/HzmMtc7hqcnCxdGLlt0Y="},"cooperative-bbf9b1ef14747d17410f2f38a6f308697335f86d4525ed6a5579905efc314ef3.js":{"logical_path":"cooperative.js","mtime":"2019-11-20T16:00:49+08:00","size":4463241,"digest":"bbf9b1ef14747d17410f2f38a6f308697335f86d4525ed6a5579905efc314ef3","integrity":"sha256-u/mx7xR0fRdBDy84pvMIaXM1+G1FJe1qVXmQXvwxTvM="},"cooperative-c36bba05d6a13482ccb6c3696ba5d750841dec9cae7a8043a0318c34c3a4638e.css":{"logical_path":"cooperative.css","mtime":"2019-11-17T09:36:46+08:00","size":849736,"digest":"c36bba05d6a13482ccb6c3696ba5d750841dec9cae7a8043a0318c34c3a4638e","integrity":"sha256-w2u6BdahNILMtsNpa6XXUIQd7JyueoBDoDGMNMOkY44="},"application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js":{"logical_path":"application.js","mtime":"2019-11-20T16:00:49+08:00","size":600706,"digest":"9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb","integrity":"sha256-nPvD15JZmh0N5ce4QgnhwrLmAzbw8B4Z8FgWY5GHCPs="},"application-8c9d6bb61c50908f584b3070c79aeb95f25c1166d39e07da5e95438b39ca0de9.css":{"logical_path":"application.css","mtime":"2019-10-21T22:52:15+08:00","size":436995,"digest":"8c9d6bb61c50908f584b3070c79aeb95f25c1166d39e07da5e95438b39ca0de9","integrity":"sha256-jJ1rthxQkI9YSzBwx5rrlfJcEWbTngfaXpVDiznKDek="},"admin-f1a4d30772fa41e1edd288c1b1d39deeac7481d590b13a886953adf2f2e4f47c.js":{"logical_path":"admin.js","mtime":"2019-11-29T15:05:39+08:00","size":4583647,"digest":"f1a4d30772fa41e1edd288c1b1d39deeac7481d590b13a886953adf2f2e4f47c","integrity":"sha256-8aTTB3L6QeHt0ojBsdOd7qx0gdWQsTqIaVOt8vLk9Hw="},"admin-0172e0b18d559d9648df58d7954e4843256ba2d2eda03543b7cc6c955c8e2eb3.js":{"logical_path":"admin.js","mtime":"2019-12-02T10:58:14+08:00","size":4583648,"digest":"0172e0b18d559d9648df58d7954e4843256ba2d2eda03543b7cc6c955c8e2eb3","integrity":"sha256-AXLgsY1VnZZI31jXlU5IQyVrotLtoDVDt8xslVyOLrM="},"admin-6da32058962cbfc9483b468c26d5f9b968b5f970a2031f73952863c554cd6254.js":{"logical_path":"admin.js","mtime":"2019-12-02T17:44:43+08:00","size":4583680,"digest":"6da32058962cbfc9483b468c26d5f9b968b5f970a2031f73952863c554cd6254","integrity":"sha256-baMgWJYsv8lIO0aMJtX5uWi1+XCiAx9zlShjxVTNYlQ="},"admin-64b90a423722da851a01011acac435b5f29f25e3b741e78854ea1a40742d89f7.js":{"logical_path":"admin.js","mtime":"2019-12-06T11:19:59+08:00","size":4584886,"digest":"64b90a423722da851a01011acac435b5f29f25e3b741e78854ea1a40742d89f7","integrity":"sha256-ZLkKQjci2oUaAQEaysQ1tfKfJeO3QeeIVOoaQHQtifc="},"admin-38dd06255909789c15bb1ae3d2c6b25064dd59d463ed996af2cd93b7aa08626b.css":{"logical_path":"admin.css","mtime":"2019-12-06T11:00:53+08:00","size":870104,"digest":"38dd06255909789c15bb1ae3d2c6b25064dd59d463ed996af2cd93b7aa08626b","integrity":"sha256-ON0GJVkJeJwVuxrj0sayUGTdWdRj7Zlq8s2Tt6oIYms="},"admin-f1565aa714ea18c95e226d27a3685c4b81544a9b531f4d14f88f6aa43cc9f2fa.css":{"logical_path":"admin.css","mtime":"2019-12-06T11:33:41+08:00","size":870109,"digest":"f1565aa714ea18c95e226d27a3685c4b81544a9b531f4d14f88f6aa43cc9f2fa","integrity":"sha256-8VZapxTqGMleIm0no2hcS4FUSptTH00U+I9qpDzJ8vo="},"admin-7e5aa975448fc470736f18156994f5b445b131ed1dbce9fad3b2d1415a0947d6.js":{"logical_path":"admin.js","mtime":"2019-12-06T15:35:42+08:00","size":4584963,"digest":"7e5aa975448fc470736f18156994f5b445b131ed1dbce9fad3b2d1415a0947d6","integrity":"sha256-flqpdUSPxHBzbxgVaZT1tEWxMe0dvOn607LRQVoJR9Y="},"admin-7cf88529e1f8fdafe22b3aef833fbba4898c4a69fdc4043dc9f1796c6f100ead.css":{"logical_path":"admin.css","mtime":"2019-12-06T18:03:36+08:00","size":870265,"digest":"7cf88529e1f8fdafe22b3aef833fbba4898c4a69fdc4043dc9f1796c6f100ead","integrity":"sha256-fPiFKeH4/a/iKzrvgz+7pImMSmn9xAQ9yfF5bG8QDq0="},"admin-aaff9ffea9f10689b47c00147b5cc6650961733dbc8ca30d23eb9afc2e6ee3dd.js":{"logical_path":"admin.js","mtime":"2019-12-06T18:20:13+08:00","size":4584962,"digest":"aaff9ffea9f10689b47c00147b5cc6650961733dbc8ca30d23eb9afc2e6ee3dd","integrity":"sha256-qv+f/qnxBom0fAAUe1zGZQlhcz28jKMNI+ua/C5u490="},"admin-ab3a763c3d168f05c11ca0a9f0cadc3030dc9f0ae0317085d9032d47643f7488.js":{"logical_path":"admin.js","mtime":"2019-12-26T16:08:18+08:00","size":4607541,"digest":"ab3a763c3d168f05c11ca0a9f0cadc3030dc9f0ae0317085d9032d47643f7488","integrity":"sha256-qzp2PD0WjwXBHKCp8MrcMDDcnwrgMXCF2QMtR2Q/dIg="},"admin-31e7cc6208d66802217e899555cd22920bb7d043578b1b16250e09dbe76b7bc6.css":{"logical_path":"admin.css","mtime":"2019-12-10T16:46:58+08:00","size":843730,"digest":"31e7cc6208d66802217e899555cd22920bb7d043578b1b16250e09dbe76b7bc6","integrity":"sha256-MefMYgjWaAIhfomVVc0ikgu30ENXixsWJQ4J2+dre8Y="},"college-893ba916d2b043f4b751cacc104de43d14e0db0b4001822f996e803bacbda169.css":{"logical_path":"college.css","mtime":"2019-12-10T16:46:58+08:00","size":589973,"digest":"893ba916d2b043f4b751cacc104de43d14e0db0b4001822f996e803bacbda169","integrity":"sha256-iTupFtKwQ/S3UcrMEE3kPRTg2wtAAYIvmW6AO6y9oWk="},"cooperative-bef1905d0c3e15357e3a376225923847b33f7388d49741062fe3cb20346da315.css":{"logical_path":"cooperative.css","mtime":"2019-12-10T16:46:58+08:00","size":824051,"digest":"bef1905d0c3e15357e3a376225923847b33f7388d49741062fe3cb20346da315","integrity":"sha256-vvGQXQw+FTV+OjdiJZI4R7M/c4jUl0EGL+PLIDRtoxU="},"application-8853af8ba870ca7fc8bdae048322f1c5a5caa26f1df87dbbf0c6d04232c4ac4e.css":{"logical_path":"application.css","mtime":"2019-10-21T22:52:15+08:00","size":418844,"digest":"8853af8ba870ca7fc8bdae048322f1c5a5caa26f1df87dbbf0c6d04232c4ac4e","integrity":"sha256-iFOvi6hwyn/Iva4EgyLxxaXKom8d+H278MbQQjLErE4="},"admin-f16eb906c3b6dce2c588677637473a128cd7b3d56c8d15b5eba621e9df586f37.js":{"logical_path":"admin.js","mtime":"2020-01-03T16:30:22+08:00","size":4610769,"digest":"f16eb906c3b6dce2c588677637473a128cd7b3d56c8d15b5eba621e9df586f37","integrity":"sha256-8W65BsO23OLFiGd2N0c6EozXs9VsjRW166Yh6d9Ybzc="},"admin-bed977ce14a5b8a1dcb718a4faa7971f54aad01ed4fa9b01a3d2ee333ed36cf8.js":{"logical_path":"admin.js","mtime":"2020-01-03T17:41:50+08:00","size":4611577,"digest":"bed977ce14a5b8a1dcb718a4faa7971f54aad01ed4fa9b01a3d2ee333ed36cf8","integrity":"sha256-vtl3zhSluKHctxik+qeXH1Sq0B7U+psBo9LuMz7TbPg="},"admin-d10431d175587afdb06f99d563f03b9890002351aa3b9a5e568d0396fd5cfe22.js":{"logical_path":"admin.js","mtime":"2020-01-31T16:18:47+08:00","size":4613282,"digest":"d10431d175587afdb06f99d563f03b9890002351aa3b9a5e568d0396fd5cfe22","integrity":"sha256-0QQx0XVYev2wb5nVY/A7mJAAI1GqO5peVo0Dlv1c/iI="},"admin-809fe775cb1fb357743738f65e0761be6b9fbe09dcd7062559f22dd87f1e059c.js":{"logical_path":"admin.js","mtime":"2020-02-04T19:26:18+08:00","size":4621538,"digest":"809fe775cb1fb357743738f65e0761be6b9fbe09dcd7062559f22dd87f1e059c","integrity":"sha256-gJ/ndcsfs1d0Nzj2Xgdhvmufvgnc1wYlWfIt2H8eBZw="},"admin-15ad6e029d1195111f661b61d8252ec40b1e453c44c489c661c8fa3211ff62b2.css":{"logical_path":"admin.css","mtime":"2020-02-05T14:22:08+08:00","size":846651,"digest":"15ad6e029d1195111f661b61d8252ec40b1e453c44c489c661c8fa3211ff62b2","integrity":"sha256-Fa1uAp0RlREfZhth2CUuxAseRTxExInGYcj6MhH/YrI="},"admin-c4d3fa2b049fa3abe12d80939ccd28ab8a824b1469cdb2c72f7daf1483ab6da1.js":{"logical_path":"admin.js","mtime":"2020-02-05T11:49:01+08:00","size":4622038,"digest":"c4d3fa2b049fa3abe12d80939ccd28ab8a824b1469cdb2c72f7daf1483ab6da1","integrity":"sha256-xNP6KwSfo6vhLYCTnM0oq4qCSxRpzbLHL32vFIOrbaE="},"admin-d4133a4bdcbfd4ca5d2dd49735008d8db50f25757650f0b13a62e254dfb201b4.js":{"logical_path":"admin.js","mtime":"2020-02-05T16:36:51+08:00","size":4624767,"digest":"d4133a4bdcbfd4ca5d2dd49735008d8db50f25757650f0b13a62e254dfb201b4","integrity":"sha256-1BM6S9y/1MpdLdSXNQCNjbUPJXV2UPCxOmLiVN+yAbQ="},"admin-7c523d78203d02769553abade33413cdf663eea3fe1d15ee07ab449240a10a9a.js":{"logical_path":"admin.js","mtime":"2020-02-19T15:51:39+08:00","size":4570082,"digest":"7c523d78203d02769553abade33413cdf663eea3fe1d15ee07ab449240a10a9a","integrity":"sha256-fFI9eCA9AnaVU6ut4zQTzfZj7qP+HRXuB6tEkkChCpo="},"college-f9c1b76699b39750bee976e1dbb5d832fdce2fb05edaf1b818c6e76fdf5ba77c.js":{"logical_path":"college.js","mtime":"2020-02-07T16:06:25+08:00","size":3505839,"digest":"f9c1b76699b39750bee976e1dbb5d832fdce2fb05edaf1b818c6e76fdf5ba77c","integrity":"sha256-+cG3Zpmzl1C+6Xbh27XYMv3OL7Be2vG4GMbnb99bp3w="},"college-2065ab3f84df62eb2ca345c83198920fad15cc3b93baf22c091f3c6bfced11b4.css":{"logical_path":"college.css","mtime":"2020-02-05T14:22:08+08:00","size":586787,"digest":"2065ab3f84df62eb2ca345c83198920fad15cc3b93baf22c091f3c6bfced11b4","integrity":"sha256-IGWrP4TfYusso0XIMZiSD60VzDuTuvIsCR88a/ztEbQ="},"cooperative-2efdf753e39599e78fb02527e864784df9a34dd971576db8231b63f618f9786e.js":{"logical_path":"cooperative.js","mtime":"2020-02-10T22:03:17+08:00","size":4413858,"digest":"2efdf753e39599e78fb02527e864784df9a34dd971576db8231b63f618f9786e","integrity":"sha256-Lv33U+OVmeePsCUn6GR4TfmjTdlxV224Ixtj9hj5eG4="},"cooperative-20747152aeb5e0bca475a49899a771a58d73be01d62c8f39396540c4f0abb10c.css":{"logical_path":"cooperative.css","mtime":"2020-02-05T14:22:08+08:00","size":826122,"digest":"20747152aeb5e0bca475a49899a771a58d73be01d62c8f39396540c4f0abb10c","integrity":"sha256-IHRxUq614LykdaSYmadxpY1zvgHWLI85OWVAxPCrsQw="},"application-47eaf72a32e7cee15e04ee7632fbbaadd5efd1129bfe0978d28d9b84ecec913e.js":{"logical_path":"application.js","mtime":"2020-02-07T16:06:25+08:00","size":551318,"digest":"47eaf72a32e7cee15e04ee7632fbbaadd5efd1129bfe0978d28d9b84ecec913e","integrity":"sha256-R+r3KjLnzuFeBO52Mvu6rdXv0RKb/gl40o2bhOzskT4="},"application-d3da385d89dfa7edfbecc09573322b132ffc5580cbd185d5f7a74d5358881815.css":{"logical_path":"application.css","mtime":"2019-09-09T09:26:59+08:00","size":419785,"digest":"d3da385d89dfa7edfbecc09573322b132ffc5580cbd185d5f7a74d5358881815","integrity":"sha256-09o4XYnfp+377MCVczIrEy/8VYDL0YXV96dNU1iIGBU="},"admin-8d7c2001aaf2599bddad06c5bc30526b059780d9fda2f01797524c0b8a1c43c3.js":{"logical_path":"admin.js","mtime":"2020-02-22T18:21:49+08:00","size":4636232,"digest":"8d7c2001aaf2599bddad06c5bc30526b059780d9fda2f01797524c0b8a1c43c3","integrity":"sha256-jXwgAaryWZvdrQbFvDBSawWXgNn9ovAXl1JMC4ocQ8M="},"admin-343a62865a99bebf4247a945a48400b472f76196fb80e86b4cce6b2cc480d306.css":{"logical_path":"admin.css","mtime":"2019-12-23T10:10:47+08:00","size":838584,"digest":"343a62865a99bebf4247a945a48400b472f76196fb80e86b4cce6b2cc480d306","integrity":"sha256-NDpihlqZvr9CR6lFpIQAtHL3YZb7gOhrTM5rLMSA0wY="},"college-461b4e52a77522c5ff4341992359aba660dc26d7d985e394b69d8b87db954a14.css":{"logical_path":"college.css","mtime":"2019-11-19T17:05:09+08:00","size":578720,"digest":"461b4e52a77522c5ff4341992359aba660dc26d7d985e394b69d8b87db954a14","integrity":"sha256-RhtOUqd1IsX/Q0GZI1mrpmDcJtfZheOUtp2Lh9uVShQ="},"cooperative-286bab0b704bc8d5295bee551a48f78e1fe8713e0e50c65059cf7a65ed09bbf0.js":{"logical_path":"cooperative.js","mtime":"2020-02-09T13:28:20+08:00","size":4478065,"digest":"286bab0b704bc8d5295bee551a48f78e1fe8713e0e50c65059cf7a65ed09bbf0","integrity":"sha256-KGurC3BLyNUpW+5VGkj3jh/ocT4OUMZQWc96Ze0Ju/A="},"cooperative-716b37420680ce5fb3bedf40508ea89e07f4d7060b412b832c8b4e9b09ac024d.css":{"logical_path":"cooperative.css","mtime":"2019-11-19T17:05:09+08:00","size":818055,"digest":"716b37420680ce5fb3bedf40508ea89e07f4d7060b412b832c8b4e9b09ac024d","integrity":"sha256-cWs3QgaAzl+zvt9AUI6ongf01wYLQSuDLItOmwmsAk0="},"application-e194b45894d0c5240d954851211a86516ee6ad871ca99bf7783ce44aeb8c0687.css":{"logical_path":"application.css","mtime":"2019-11-19T17:04:45+08:00","size":413848,"digest":"e194b45894d0c5240d954851211a86516ee6ad871ca99bf7783ce44aeb8c0687","integrity":"sha256-4ZS0WJTQxSQNlUhRIRqGUW7mrYccqZv3eDzkSuuMBoc="},"admin-04ff7d4c76f406e0df9e02923038d175f26ee098d43e266c2138b07163a2da9e.js":{"logical_path":"admin.js","mtime":"2020-02-25T12:09:35+08:00","size":4635520,"digest":"04ff7d4c76f406e0df9e02923038d175f26ee098d43e266c2138b07163a2da9e","integrity":"sha256-BP99THb0BuDfngKSMDjRdfJu4JjUPiZsITiwcWOi2p4="},"admin-46d2460ea8c06a885f92df73240e8eb84d320402bbdf580ffe3e038d07fdda3d.js":{"logical_path":"admin.js","mtime":"2020-02-25T13:17:26+08:00","size":4635586,"digest":"46d2460ea8c06a885f92df73240e8eb84d320402bbdf580ffe3e038d07fdda3d","integrity":"sha256-RtJGDqjAaohfkt9zJA6OuE0yBAK731gP/j4DjQf92j0="},"admin-b8f26db185886f99021da91f0e2c1a8cc4ced4d1b895794751d11b7a9f45de98.js":{"logical_path":"admin.js","mtime":"2020-02-25T17:16:13+08:00","size":4637067,"digest":"b8f26db185886f99021da91f0e2c1a8cc4ced4d1b895794751d11b7a9f45de98","integrity":"sha256-uPJtsYWIb5kCHakfDiwajMTO1NG4lXlHUdEbep9F3pg="},"admin-8cb0e9724b33e253621a5f1020bdc270f828cb7d602de8434d3cc606e0b4dbc2.js":{"logical_path":"admin.js","mtime":"2020-02-25T17:51:23+08:00","size":4637275,"digest":"8cb0e9724b33e253621a5f1020bdc270f828cb7d602de8434d3cc606e0b4dbc2","integrity":"sha256-jLDpcksz4lNiGl8QIL3CcPgoy31gLehDTTzGBuC028I="},"admin-b2526c7eefd8212cde88c53131e8a7dd02f4d96e6d9e262d31dbca516addc979.js":{"logical_path":"admin.js","mtime":"2021-08-31T14:49:02+08:00","size":1600367,"digest":"b2526c7eefd8212cde88c53131e8a7dd02f4d96e6d9e262d31dbca516addc979","integrity":"sha256-slJsfu/YISzeiMUxMein3QL02W5tniYtMdvKUWrdyXk="},"admin-719988f975840fa3b187ef2594aecb8494218f82342377a1f3c71731f68fbf61.css":{"logical_path":"admin.css","mtime":"2021-08-06T09:41:07+08:00","size":366126,"digest":"719988f975840fa3b187ef2594aecb8494218f82342377a1f3c71731f68fbf61","integrity":"sha256-cZmI+XWED6Oxh+8llK7LhJQhj4I0I3eh88cXMfaPv2E="},"college-bd7fb1ab1da64733a0172f89c8b9d5cfe4db736bb3ee51e0ca0701b40e2e49cc.js":{"logical_path":"college.js","mtime":"2021-08-06T09:41:07+08:00","size":1090655,"digest":"bd7fb1ab1da64733a0172f89c8b9d5cfe4db736bb3ee51e0ca0701b40e2e49cc","integrity":"sha256-vX+xqx2mRzOgFy+JyLnVz+Tbc2uz7lHgygcBtA4uScw="},"college-fe5b44c2d2fe8fcd24143a2cb9df8254be9104d3fe4e3da0f3e3317838a16ebc.css":{"logical_path":"college.css","mtime":"2021-07-16T11:58:16+08:00","size":218568,"digest":"fe5b44c2d2fe8fcd24143a2cb9df8254be9104d3fe4e3da0f3e3317838a16ebc","integrity":"sha256-/ltEwtL+j80kFDosud+CVL6RBNP+Tj2g8+MxeDihbrw="},"cooperative-1f8e268f2c629e3453e71d3ad4bc2c043e73989a8804598ad807c4050ee3c346.js":{"logical_path":"cooperative.js","mtime":"2021-08-06T09:41:07+08:00","size":1495462,"digest":"1f8e268f2c629e3453e71d3ad4bc2c043e73989a8804598ad807c4050ee3c346","integrity":"sha256-H44mjyxinjRT5x061LwsBD5zmJqIBFmK2AfEBQ7jw0Y="},"cooperative-cf4adc259ee46508f0fba0ccaa4f2c3beb33dbb715cc354ec30fbb7db729ca77.css":{"logical_path":"cooperative.css","mtime":"2021-07-16T11:58:17+08:00","size":354672,"digest":"cf4adc259ee46508f0fba0ccaa4f2c3beb33dbb715cc354ec30fbb7db729ca77","integrity":"sha256-z0rcJZ7kZQjw+6DMqk8sO+sz27cVzDVOww+7fbcpync="},"oauth-79ad9eb7fd1c8cc81aed569f2c933d0bbd5a823dc450a1038c034eef2a7b6b9c.css":{"logical_path":"oauth.css","mtime":"2021-07-16T11:58:16+08:00","size":1497,"digest":"79ad9eb7fd1c8cc81aed569f2c933d0bbd5a823dc450a1038c034eef2a7b6b9c","integrity":"sha256-ea2et/0cjMga7VafLJM9C71agj3EUKEDjANO7yp7a5w="},"application-860dba70259cdc6c5c72c499f8be717c050d517f1fd6a1543c0b6863af987000.js":{"logical_path":"application.js","mtime":"2021-08-06T09:41:07+08:00","size":506517,"digest":"860dba70259cdc6c5c72c499f8be717c050d517f1fd6a1543c0b6863af987000","integrity":"sha256-hg26cCWc3GxccsSZ+L5xfAUNUX8f1qFUPAtoY6+YcAA="},"application-346b678500708f3f4e9cd72c46d1e9e54aaf76e0478b766519ac083bf93fbcff.css":{"logical_path":"application.css","mtime":"2021-07-16T11:58:16+08:00","size":159095,"digest":"346b678500708f3f4e9cd72c46d1e9e54aaf76e0478b766519ac083bf93fbcff","integrity":"sha256-NGtnhQBwjz9OnNcsRtHp5UqvduBHi3ZlGawIO/k/vP8="}},"assets":{"admin.js":"admin-b2526c7eefd8212cde88c53131e8a7dd02f4d96e6d9e262d31dbca516addc979.js","admin.css":"admin-719988f975840fa3b187ef2594aecb8494218f82342377a1f3c71731f68fbf61.css","font-awesome/fontawesome-webfont.eot":"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot","font-awesome/fontawesome-webfont.woff2":"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2","font-awesome/fontawesome-webfont.woff":"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff","font-awesome/fontawesome-webfont.ttf":"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf","font-awesome/fontawesome-webfont.svg":"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg","college.js":"college-bd7fb1ab1da64733a0172f89c8b9d5cfe4db736bb3ee51e0ca0701b40e2e49cc.js","college.css":"college-fe5b44c2d2fe8fcd24143a2cb9df8254be9104d3fe4e3da0f3e3317838a16ebc.css","cooperative.js":"cooperative-1f8e268f2c629e3453e71d3ad4bc2c043e73989a8804598ad807c4050ee3c346.js","cooperative.css":"cooperative-cf4adc259ee46508f0fba0ccaa4f2c3beb33dbb715cc354ec30fbb7db729ca77.css","logo.png":"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png","application.js":"application-860dba70259cdc6c5c72c499f8be717c050d517f1fd6a1543c0b6863af987000.js","application.css":"application-346b678500708f3f4e9cd72c46d1e9e54aaf76e0478b766519ac083bf93fbcff.css","oauth.css":"oauth-79ad9eb7fd1c8cc81aed569f2c933d0bbd5a823dc450a1038c034eef2a7b6b9c.css"}} \ No newline at end of file diff --git a/public/assets/admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js b/public/assets/admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js deleted file mode 100644 index d7be53fe..00000000 --- a/public/assets/admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js +++ /dev/null @@ -1,49 +0,0 @@ -function createMDEditor(t,e){var n={height:600,path:"/editormd/lib/",syncScrolling:"single",tex:!0,tocm:!0,emoji:!0,taskList:!0,codeFold:!0,searchReplace:!0,htmlDecode:"style,script,iframe",sequenceDiagram:!0,autoFocus:!1,toolbarIcons:function(){return["bold","italic","|","list-ul","list-ol","|","code","code-block","|","image","table","|","watch","clear"]},saveHTMLToTextarea:!0,dialogMaskOpacity:.6,imageUpload:!0,imageFormats:["jpg","jpeg","gif","png","bmp","webp","JPG","JPEG","GIF","PNG","BMP","WEBP"],imageUploadURL:"/api/attachments.json"},i=$.extend({},n,e);return editormd(t,i)}function ajaxErrorNotifyHandler(t){var e="";return e=500!==t.status?t.responseJSON.message:"\u7cfb\u7edf\u9519\u8bef",$.notify({message:e},{type:"danger"})}function resetFileInputFunc(t){t.after(t.clone().val("")),t.remove()}function customConfirm(t){var e={title:"\u63d0\u793a",buttons:{ok:{text:"\u786e\u8ba4",btnClass:"btn btn-primary",action:t.ok},cancel:{text:"\u53d6\u6d88",btnClass:"btn btn-secondary",action:t.cancel}}};return $.confirm($.extend({},e,t))}function customLoading(t){var e,n={content:t.ajax,contentLoaded:function(){setTimeout(function(){e.close()},200)}};return e=$.confirm($.extend({},n,t))}function show_success_flash(t){$.notify({message:t||"\u64cd\u4f5c\u6210\u529f"},{type:"success"})}function showSuccessFlash(t){$.notify({message:t||"\u64cd\u4f5c\u6210\u529f"},{type:"success"})}function showErrorNotify(t){$.notify({message:t||"\u64cd\u4f5c\u5931\u8d25"},{type:"danger"})}function show_add_manager(t){$(".auth-schools-user-add").modal("show"),$(".auth-schools-user-add").find("#school_id_input").val(t)}function addSponsor(t){var e='';$(t).parents(".sponsorPanel").append(e)}function del_sponsor(t){$(t).parents(".sponsor_label").remove()}function change_total(t){var e=parseInt($(t).val()),n=$(t).parents(".small_panel_item").attr("attr_line"),i=$(t).parents(".large_panel_part").attr("attr_line");console.log(i),console.log(n);var r=parseInt($("#task_Input_"+n).find(".task_Input_div").length),o="";if(r\u4efb\u52a1'+(r+a+1)+'\n
\n\n
\n';$("#task_Input_"+n).append(o)}else{var s=r-e;console.log(r),console.log(e);var l=parseInt($("#task_Input_"+n).find(".task_Input_div:last").index());console.log(l);var u=l-s;console.log(u),0==e?$("#task_Input_"+n).empty():$("#task_Input_"+n).find(".task_Input_div:gt("+u+")").remove()}}function Del_tab(t){$(t).parents(".large_panel_part").remove()}function addNewTab(t){if(0<$(".new-stage-form").length)alert("\u8bf7\u5148\u4fdd\u5b58\u4e0a\u4e00\u4e2atab");else{var e=parseInt($("#large_panel").find(".large_panel_part").length)+1,n='
\n tab\u6807\u9898\n
\n \n
\n \u603b\u6392\u884c\u699c\u5360\u6bd4:\n
\n \n
%\n \n \u5220\u9664\n \u4fdd\u5b58\n
\n
\n
\n \u7b2c1\u9636\u6bb5\n
\n
\n
\u6709\u6548\u65f6\u95f4:\n
\n \n
\n ~\n
\n \n
\n
\u603b\u4efb\u52a1\u6570:\n
\n \n
\n
\u6210\u7ee9\u6765\u6e90:\n
\n \n
\n
\n
\n
\n \u4efb\u52a11\n
\n \n
\n
\n
\n \u4efb\u52a12\n
\n \n
\n
\n
\n \u4efb\u52a13\n
\n \n
\n
\n
\n
\n \n \u5220\u9664\n \n
\n
';$("#large_panel").append(n)}}function showMessageModal(t,e){var n=$(".modal.admin-message-modal"),i=n.find(".submit-btn");i.unbind(),e!==undefined&&"function"==typeof e&&i.on("click",e),n.find(".modal-body").html(t),n.modal("show")}function show_success_flash(){$.notify({message:"\u64cd\u4f5c\u6210\u529f"},{type:"success"})}(function(){var t=this;(function(){(function(){this.Rails={linkClickSelector:"a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]",buttonClickSelector:{selector:"button[data-remote]:not([form]), button[data-confirm]:not([form])",exclude:"form button"},inputChangeSelector:"select[data-remote], input[data-remote], textarea[data-remote]",formSubmitSelector:"form",formInputClickSelector:"form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])",formDisableSelector:"input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled",formEnableSelector:"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled",fileInputSelector:"input[name][type=file]:not([disabled])",linkDisableSelector:"a[data-disable-with], a[data-disable]",buttonDisableSelector:"button[data-remote][data-disable-with], button[data-remote][data-disable]"}}).call(this)}).call(t);var y=t.Rails;(function(){(function(){var e;e=null,y.loadCSPNonce=function(){var t;return e=null!=(t=document.querySelector("meta[name=csp-nonce]"))?t.content:void 0},y.cspNonce=function(){return null!=e?e:y.loadCSPNonce()}}).call(this),function(){var i,n;n=Element.prototype.matches||Element.prototype.matchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector||Element.prototype.oMatchesSelector||Element.prototype.webkitMatchesSelector,y.matches=function(t,e){return null!=e.exclude?n.call(t,e.selector)&&!n.call(t,e.exclude):n.call(t,e)},i="_ujsData",y.getData=function(t,e){var n;return null!=(n=t[i])?n[e]:void 0},y.setData=function(t,e,n){return null==t[i]&&(t[i]={}),t[i][e]=n},y.$=function(t){return Array.prototype.slice.call(document.querySelectorAll(t))}}.call(this),function(){var n,i,r;n=y.$,r=y.csrfToken=function(){var t;return(t=document.querySelector("meta[name=csrf-token]"))&&t.content},i=y.csrfParam=function(){var t;return(t=document.querySelector("meta[name=csrf-param]"))&&t.content},y.CSRFProtection=function(t){var e;if(null!=(e=r()))return t.setRequestHeader("X-CSRF-Token",e)},y.refreshCSRFTokens=function(){var t,e;if(e=r(),t=i(),null!=e&&null!=t)return n('form input[name="'+t+'"]').forEach(function(t){return t.value=e})}}.call(this),function(){var r,e,o,n;o=y.matches,"function"!=typeof(r=window.CustomEvent)&&((r=function(t,e){var n;return(n=document.createEvent("CustomEvent")).initCustomEvent(t,e.bubbles,e.cancelable,e.detail),n}).prototype=window.Event.prototype,n=r.prototype.preventDefault,r.prototype.preventDefault=function(){var t;return t=n.call(this),this.cancelable&&!this.defaultPrevented&&Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}}),t}),e=y.fire=function(t,e,n){var i;return i=new r(e,{bubbles:!0,cancelable:!0,detail:n}),t.dispatchEvent(i),!i.defaultPrevented},y.stopEverything=function(t){return e(t.target,"ujs:everythingStopped"),t.preventDefault(),t.stopPropagation(),t.stopImmediatePropagation()},y.delegate=function(t,n,e,i){return t.addEventListener(e,function(t){var e;for(e=t.target;e instanceof Element&&!o(e,n);)e=e.parentNode;if(e instanceof Element&&!1===i.call(e,t))return t.preventDefault(),t.stopPropagation()})}}.call(this),function(){var e,i,t,o,r,a;o=y.cspNonce,i=y.CSRFProtection,y.fire,e={"*":"*/*",text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript",script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},y.ajax=function(n){var i;return n=r(n),i=t(n,function(){var t,e;return e=a(null!=(t=i.response)?t:i.responseText,i.getResponseHeader("Content-Type")),2===Math.floor(i.status/100)?"function"==typeof n.success&&n.success(e,i.statusText,i):"function"==typeof n.error&&n.error(e,i.statusText,i),"function"==typeof n.complete?n.complete(i,i.statusText):void 0}),!(null!=n.beforeSend&&!n.beforeSend(i,n))&&(i.readyState===XMLHttpRequest.OPENED?i.send(n.data):void 0)},r=function(t){return t.url=t.url||location.href,t.type=t.type.toUpperCase(),"GET"===t.type&&t.data&&(t.url.indexOf("?")<0?t.url+="?"+t.data:t.url+="&"+t.data),null==e[t.dataType]&&(t.dataType="*"),t.accept=e[t.dataType],"*"!==t.dataType&&(t.accept+=", */*; q=0.01"),t},t=function(t,e){var n;return(n=new XMLHttpRequest).open(t.type,t.url,!0),n.setRequestHeader("Accept",t.accept),"string"==typeof t.data&&n.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),t.crossDomain||n.setRequestHeader("X-Requested-With","XMLHttpRequest"),i(n),n.withCredentials=!!t.withCredentials,n.onreadystatechange=function(){if(n.readyState===XMLHttpRequest.DONE)return e(n)},n},a=function(t,e){var n,i;if("string"==typeof t&&"string"==typeof e)if(e.match(/\bjson\b/))try{t=JSON.parse(t)}catch(r){}else if(e.match(/\b(?:java|ecma)script\b/))(i=document.createElement("script")).setAttribute("nonce",o()),i.text=t,document.head.appendChild(i).parentNode.removeChild(i);else if(e.match(/\b(xml|html|svg)\b/)){n=new DOMParser,e=e.replace(/;.+/,"");try{t=n.parseFromString(t,e)}catch(r){}}return t},y.href=function(t){return t.href},y.isCrossDomain=function(t){var e,n;(e=document.createElement("a")).href=location.href,n=document.createElement("a");try{return n.href=t,!((!n.protocol||":"===n.protocol)&&!n.host||e.protocol+"//"+e.host==n.protocol+"//"+n.host)}catch(i){return i,!0}}}.call(this),function(){var r,o;r=y.matches,o=function(t){return Array.prototype.slice.call(t)},y.serializeElement=function(t,e){var n,i;return n=[t],r(t,"form")&&(n=o(t.elements)),i=[],n.forEach(function(e){if(e.name&&!e.disabled)return r(e,"select")?o(e.options).forEach(function(t){if(t.selected)return i.push({name:e.name,value:t.value})}):e.checked||-1===["radio","checkbox","submit"].indexOf(e.type)?i.push({name:e.name,value:e.value}):void 0}),e&&i.push(e),i.map(function(t){return null!=t.name?encodeURIComponent(t.name)+"="+encodeURIComponent(t.value):t}).join("&")},y.formElements=function(t,e){return r(t,"form")?o(t.elements).filter(function(t){return r(t,e)}):o(t.querySelectorAll(e))}}.call(this),function(){var e,o,n;o=y.fire,n=y.stopEverything,y.handleConfirm=function(t){if(!e(this))return n(t)},e=function(t){var e,n,i;if(!(i=t.getAttribute("data-confirm")))return!0;if(e=!1,o(t,"confirm")){try{e=confirm(i)}catch(r){}n=o(t,"confirm:complete",[e])}return e&&n}}.call(this),function(){var n,i,r,o,a,s,e,l,u,c,d;u=y.matches,l=y.getData,c=y.setData,d=y.stopEverything,e=y.formElements,y.handleDisabledElement=function(t){if(this.disabled)return d(t)},y.enableElement=function(t){var e;return e=t instanceof Event?t.target:t,u(e,y.linkDisableSelector)?s(e):u(e,y.buttonDisableSelector)||u(e,y.formEnableSelector)?o(e):u(e,y.formSubmitSelector)?a(e):void 0},y.disableElement=function(t){var e;return e=t instanceof Event?t.target:t,u(e,y.linkDisableSelector)?r(e):u(e,y.buttonDisableSelector)||u(e,y.formDisableSelector)?n(e):u(e,y.formSubmitSelector)?i(e):void 0},r=function(t){var e;return null!=(e=t.getAttribute("data-disable-with"))&&(c(t,"ujs:enable-with",t.innerHTML),t.innerHTML=e),t.addEventListener("click",d),c(t,"ujs:disabled",!0)},s=function(t){var e;return null!=(e=l(t,"ujs:enable-with"))&&(t.innerHTML=e,c(t,"ujs:enable-with",null)),t.removeEventListener("click",d),c(t,"ujs:disabled",null)},i=function(t){return e(t,y.formDisableSelector).forEach(n)},n=function(t){var e;return null!=(e=t.getAttribute("data-disable-with"))&&(u(t,"button")?(c(t,"ujs:enable-with",t.innerHTML),t.innerHTML=e):(c(t,"ujs:enable-with",t.value),t.value=e)),t.disabled=!0,c(t,"ujs:disabled",!0)},a=function(t){return e(t,y.formEnableSelector).forEach(o)},o=function(t){var e;return null!=(e=l(t,"ujs:enable-with"))&&(u(t,"button")?t.innerHTML=e:t.value=e,c(t,"ujs:enable-with",null)),t.disabled=!1,c(t,"ujs:disabled",null)}}.call(this),function(){var l;l=y.stopEverything,y.handleMethod=function(t){var e,n,i,r,o,a,s;if(s=(a=this).getAttribute("data-method"))return o=y.href(a),n=y.csrfToken(),e=y.csrfParam(),i=document.createElement("form"),r="",null==e||null==n||y.isCrossDomain(o)||(r+=""),r+='',i.method="post",i.action=o,i.target=a.target,i.innerHTML=r,i.style.display="none",document.body.appendChild(i),i.querySelector('[type="submit"]').click(),l(t)}}.call(this),function(){var l,u,c,d,h,f,p,g,m,v=[].slice;f=y.matches,c=y.getData,g=y.setData,u=y.fire,m=y.stopEverything,l=y.ajax,d=y.isCrossDomain,p=y.serializeElement,h=function(t){var e;return null!=(e=t.getAttribute("data-remote"))&&"false"!==e},y.handleRemote=function(t){var e,n,i,r,o,a,s;return!h(r=this)||(u(r,"ajax:before")?(s=r.getAttribute("data-with-credentials"),i=r.getAttribute("data-type")||"script",f(r,y.formSubmitSelector)?(e=c(r,"ujs:submit-button"),o=c(r,"ujs:submit-button-formmethod")||r.method,a=c(r,"ujs:submit-button-formaction")||r.getAttribute("action")||location.href,"GET"===o.toUpperCase()&&(a=a.replace(/\?.*$/,"")),"multipart/form-data"===r.enctype?(n=new FormData(r),null!=e&&n.append(e.name,e.value)):n=p(r,e),g(r,"ujs:submit-button",null),g(r,"ujs:submit-button-formmethod",null),g(r,"ujs:submit-button-formaction",null)):f(r,y.buttonClickSelector)||f(r,y.inputChangeSelector)?(o=r.getAttribute("data-method"),a=r.getAttribute("data-url"),n=p(r,r.getAttribute("data-params"))):(o=r.getAttribute("data-method"),a=y.href(r),n=r.getAttribute("data-params")),l({type:o||"GET",url:a,data:n,dataType:i,beforeSend:function(t,e){return u(r,"ajax:beforeSend",[t,e])?u(r,"ajax:send",[t]):(u(r,"ajax:stopped"),!1)},success:function(){var t;return t=1<=arguments.length?v.call(arguments,0):[],u(r,"ajax:success",t)},error:function(){var t;return t=1<=arguments.length?v.call(arguments,0):[],u(r,"ajax:error",t)},complete:function(){var t;return t=1<=arguments.length?v.call(arguments,0):[],u(r,"ajax:complete",t)},crossDomain:d(a),withCredentials:null!=s&&"false"!==s}),m(t)):(u(r,"ajax:stopped"),!1))},y.formSubmitButtonClick=function(){var t,e;if(e=(t=this).form)return t.name&&g(e,"ujs:submit-button",{name:t.name,value:t.value}),g(e,"ujs:formnovalidate-button",t.formNoValidate),g(e,"ujs:submit-button-formaction",t.getAttribute("formaction")),g(e,"ujs:submit-button-formmethod",t.getAttribute("formmethod"))},y.preventInsignificantClick=function(t){var e,n,i,r;if(r=((i=this).getAttribute("data-method")||"GET").toUpperCase(),e=i.getAttribute("data-params"),n=(t.metaKey||t.ctrlKey)&&"GET"===r&&!e,null!=t.button&&0!==t.button||n)return t.stopImmediatePropagation()}}.call(this),function(){var t,i,e,n,r,o,a,s,l,u,c,d,h,f,p;if(o=y.fire,e=y.delegate,s=y.getData,t=y.$,p=y.refreshCSRFTokens,i=y.CSRFProtection,h=y.loadCSPNonce,r=y.enableElement,n=y.disableElement,u=y.handleDisabledElement,l=y.handleConfirm,f=y.preventInsignificantClick,d=y.handleRemote,a=y.formSubmitButtonClick,c=y.handleMethod,"undefined"!=typeof jQuery&&null!==jQuery&&null!=jQuery.ajax){if(jQuery.rails)throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.");jQuery.rails=y,jQuery.ajaxPrefilter(function(t,e,n){if(!t.crossDomain)return i(n)})}y.start=function(){if(window._rails_loaded)throw new Error("rails-ujs has already been loaded!");return window.addEventListener("pageshow",function(){return t(y.formEnableSelector).forEach(function(t){if(s(t,"ujs:disabled"))return r(t)}),t(y.linkDisableSelector).forEach(function(t){if(s(t,"ujs:disabled"))return r(t)})}),e(document,y.linkDisableSelector,"ajax:complete",r),e(document,y.linkDisableSelector,"ajax:stopped",r),e(document,y.buttonDisableSelector,"ajax:complete",r),e(document,y.buttonDisableSelector,"ajax:stopped",r),e(document,y.linkClickSelector,"click",f),e(document,y.linkClickSelector,"click",u),e(document,y.linkClickSelector,"click",l),e(document,y.linkClickSelector,"click",n),e(document,y.linkClickSelector,"click",d),e(document,y.linkClickSelector,"click",c),e(document,y.buttonClickSelector,"click",f),e(document,y.buttonClickSelector,"click",u),e(document,y.buttonClickSelector,"click",l),e(document,y.buttonClickSelector,"click",n),e(document,y.buttonClickSelector,"click",d),e(document,y.inputChangeSelector,"change",u),e(document,y.inputChangeSelector,"change",l),e(document,y.inputChangeSelector,"change",d),e(document,y.formSubmitSelector,"submit",u),e(document,y.formSubmitSelector,"submit",l),e(document,y.formSubmitSelector,"submit",d),e(document,y.formSubmitSelector,"submit",function(t){return setTimeout(function(){return n(t)},13)}),e(document,y.formSubmitSelector,"ajax:send",n),e(document,y.formSubmitSelector,"ajax:complete",r),e(document,y.formInputClickSelector,"click",f),e(document,y.formInputClickSelector,"click",u),e(document,y.formInputClickSelector,"click",l),e(document,y.formInputClickSelector,"click",a),document.addEventListener("DOMContentLoaded",p),document.addEventListener("DOMContentLoaded",h),window._rails_loaded=!0},window.Rails===y&&o(document,"rails:attachBindings")&&y.start()}.call(this)}).call(this),"object"==typeof module&&module.exports?module.exports=y:"function"==typeof define&&define.amd&&define(y)}).call(this),function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.ActiveStorage={})}(this,function(t){"use strict";function e(t,e){return t(e={exports:{}},e.exports),e.exports}function a(t){var e=i(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function r(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function i(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function l(t,e,n){var i=2>>25)+i|0)&i|~n&r)+e[1]-389564586|0)<<12|o>>>20)+n|0)&n|~o&i)+e[2]+606105819|0)<<17|r>>>15)+o|0)&o|~r&n)+e[3]-1044525330|0)<<22|i>>>10)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&r|~i&o)+e[4]-176418897|0)<<7|n>>>25)+i|0)&i|~n&r)+e[5]+1200080426|0)<<12|o>>>20)+n|0)&n|~o&i)+e[6]-1473231341|0)<<17|r>>>15)+o|0)&o|~r&n)+e[7]-45705983|0)<<22|i>>>10)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&r|~i&o)+e[8]+1770035416|0)<<7|n>>>25)+i|0)&i|~n&r)+e[9]-1958414417|0)<<12|o>>>20)+n|0)&n|~o&i)+e[10]-42063|0)<<17|r>>>15)+o|0)&o|~r&n)+e[11]-1990404162|0)<<22|i>>>10)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&r|~i&o)+e[12]+1804603682|0)<<7|n>>>25)+i|0)&i|~n&r)+e[13]-40341101|0)<<12|o>>>20)+n|0)&n|~o&i)+e[14]-1502002290|0)<<17|r>>>15)+o|0)&o|~r&n)+e[15]+1236535329|0)<<22|i>>>10)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&o|r&~o)+e[1]-165796510|0)<<5|n>>>27)+i|0)&r|i&~r)+e[6]-1069501632|0)<<9|o>>>23)+n|0)&i|n&~i)+e[11]+643717713|0)<<14|r>>>18)+o|0)&n|o&~n)+e[0]-373897302|0)<<20|i>>>12)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&o|r&~o)+e[5]-701558691|0)<<5|n>>>27)+i|0)&r|i&~r)+e[10]+38016083|0)<<9|o>>>23)+n|0)&i|n&~i)+e[15]-660478335|0)<<14|r>>>18)+o|0)&n|o&~n)+e[4]-405537848|0)<<20|i>>>12)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&o|r&~o)+e[9]+568446438|0)<<5|n>>>27)+i|0)&r|i&~r)+e[14]-1019803690|0)<<9|o>>>23)+n|0)&i|n&~i)+e[3]-187363961|0)<<14|r>>>18)+o|0)&n|o&~n)+e[8]+1163531501|0)<<20|i>>>12)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i&o|r&~o)+e[13]-1444681467|0)<<5|n>>>27)+i|0)&r|i&~r)+e[2]-51403784|0)<<9|o>>>23)+n|0)&i|n&~i)+e[7]+1735328473|0)<<14|r>>>18)+o|0)&n|o&~n)+e[12]-1926607734|0)<<20|i>>>12)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i^r^o)+e[5]-378558|0)<<4|n>>>28)+i|0)^i^r)+e[8]-2022574463|0)<<11|o>>>21)+n|0)^n^i)+e[11]+1839030562|0)<<16|r>>>16)+o|0)^o^n)+e[14]-35309556|0)<<23|i>>>9)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i^r^o)+e[1]-1530992060|0)<<4|n>>>28)+i|0)^i^r)+e[4]+1272893353|0)<<11|o>>>21)+n|0)^n^i)+e[7]-155497632|0)<<16|r>>>16)+o|0)^o^n)+e[10]-1094730640|0)<<23|i>>>9)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i^r^o)+e[13]+681279174|0)<<4|n>>>28)+i|0)^i^r)+e[0]-358537222|0)<<11|o>>>21)+n|0)^n^i)+e[3]-722521979|0)<<16|r>>>16)+o|0)^o^n)+e[6]+76029189|0)<<23|i>>>9)+r|0,i=((i+=((r=((r+=((o=((o+=((n=((n+=(i^r^o)+e[9]-640364487|0)<<4|n>>>28)+i|0)^i^r)+e[12]-421815835|0)<<11|o>>>21)+n|0)^n^i)+e[15]+530742520|0)<<16|r>>>16)+o|0)^o^n)+e[2]-995338651|0)<<23|i>>>9)+r|0,i=((i+=((o=((o+=(i^((n=((n+=(r^(i|~o))+e[0]-198630844|0)<<6|n>>>26)+i|0)|~r))+e[7]+1126891415|0)<<10|o>>>22)+n|0)^((r=((r+=(n^(o|~i))+e[14]-1416354905|0)<<15|r>>>17)+o|0)|~n))+e[5]-57434055|0)<<21|i>>>11)+r|0,i=((i+=((o=((o+=(i^((n=((n+=(r^(i|~o))+e[12]+1700485571|0)<<6|n>>>26)+i|0)|~r))+e[3]-1894986606|0)<<10|o>>>22)+n|0)^((r=((r+=(n^(o|~i))+e[10]-1051523|0)<<15|r>>>17)+o|0)|~n))+e[1]-2054922799|0)<<21|i>>>11)+r|0,i=((i+=((o=((o+=(i^((n=((n+=(r^(i|~o))+e[8]+1873313359|0)<<6|n>>>26)+i|0)|~r))+e[15]-30611744|0)<<10|o>>>22)+n|0)^((r=((r+=(n^(o|~i))+e[6]-1560198380|0)<<15|r>>>17)+o|0)|~n))+e[13]+1309151649|0)<<21|i>>>11)+r|0,i=((i+=((o=((o+=(i^((n=((n+=(r^(i|~o))+e[4]-145523070|0)<<6|n>>>26)+i|0)|~r))+e[11]-1120210379|0)<<10|o>>>22)+n|0)^((r=((r+=(n^(o|~i))+e[2]+718787259|0)<<15|r>>>17)+o|0)|~n))+e[9]-343485551|0)<<21|i>>>11)+r|0,t[0]=n+t[0]|0,t[1]=i+t[1]|0,t[2]=r+t[2]|0,t[3]=o+t[3]|0}function d(t){var e,n=[];for(e=0;e<64;e+=4)n[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return n}function h(t){var e,n=[];for(e=0;e<64;e+=4)n[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return n}function i(t){var e,n,i,r,o,a,s=t.length,l=[1732584193,-271733879,-1732584194,271733878];for(e=64;e<=s;e+=64)u(l,d(t.substring(e-64,e)));for(n=(t=t.substring(e-64)).length,i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],e=0;e>2]|=t.charCodeAt(e)<<(e%4<<3);if(i[e>>2]|=128<<(e%4<<3),55>2]|=t[e]<<(e%4<<3);if(i[e>>2]|=128<<(e%4<<3),55>8*e+4&15]+g[t>>8*e&15];return n}function a(t){var e;for(e=0;e>2]|=i.charCodeAt(e)<<(e%4<<3);return this._finish(o,r),n=a(this._hash),t&&(n=f(n)),this.reset(),n},p.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},p.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},p.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},p.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},p.prototype._finish=function(t,e){var n,i,r,o=e;if(t[o>>2]|=128<<(o%4<<3),55>2]|=i[e]<<(e%4<<3);return this._finish(o,r),n=a(this._hash),t&&(n=f(n)),this.reset(),n},p.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},p.ArrayBuffer.prototype.getState=function(){var t=p.prototype.getState.call(this);return t.buff=s(t.buff),t},p.ArrayBuffer.prototype.setState=function(t){return t.buff=e(t.buff,!0),p.prototype.setState.call(this,t)},p.ArrayBuffer.prototype.destroy=p.prototype.destroy,p.ArrayBuffer.prototype._finish=p.prototype._finish,p.ArrayBuffer.hash=function(t,e){var n=a(r(new Uint8Array(t)));return e?f(n):n},p},t.exports=e()}),y=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},x=function(){function i(t,e){for(var n=0;n element inside the element. This is probably not what you meant to do!\n\nLoad your application\u2019s JavaScript bundle inside the element instead.