diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb743b9..d0a865bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ * Fix 版本库中附件下载400(#51625) * Fix loading页面优化(#51588) * Fix 提交详情页面优化(#51577) - * Fix 修复易修复制功能(#51569) + * Fix 修复疑修复制功能(#51569) * Fix 修复新建发行版用户信息显示错误的问题(#51665) * Fix 修复查看文件详细信息报错的问题(#51561) * Fix 修复提交记录中时间显示格式问题(#51526) @@ -62,7 +62,7 @@ ## [v3.0.3](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-08 * BUGFIXES - * Fix 解决易修标题过长导致的排版问题(45469) + * Fix 解决疑修标题过长导致的排版问题(45469) * Fix 解决合并请求详情页面排版错误的问题(45457) * FIX 解决转移仓库界面专有名词描述错误的问题(45455) * Fix 解决markdown格式文件自动生成数字排序的问题(45454) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 70e5b603..377b13f1 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -1,379 +1,384 @@ -class AccountsController < ApplicationController - include ApplicationHelper - - def index - render json: session - end - - # 其他平台同步注册的用户 - def remote_register - username = params[:username]&.gsub(/\s+/, "") - tip_exception("无法使用以下关键词:#{username},请重新命名") if ReversedKeyword.check_exists?(username) - email = params[:email]&.gsub(/\s+/, "") - password = params[:password] - platform = (params[:platform] || 'forge')&.gsub(/\s+/, "") - - ActiveRecord::Base.transaction do - result = autologin_register(username, email, password, platform) - if result[:message].blank? - render_ok({user: result[:user]}) - else - render_error(result[:message]) - end - end - rescue Exception => e - uid_logger_error(e.message) - tip_exception(-1, e.message) - end - - # 其他平台修改用户的信息,这边同步修改 - def remote_update - ActiveRecord::Base.transaction do - user_params = params[:user_params] - user_extension_params = params[:user_extension_params] - - u = User.find_by(login: params[:old_user_login]) - user_mail = u.try(:mail) - - if u.present? - ue = u.user_extension - u.login = user_params["login"] if user_params["login"] - u.mail = user_params["mail"] if user_params["mail"] - u.lastname = user_params["lastname"] if user_params["lastname"] - - ue.gender = user_extension_params["gender"] - ue.school_id = user_extension_params["school_id"] - ue.location = user_extension_params["location"] - ue.location_city = user_extension_params["location_city"] - ue.identity = user_extension_params["identity"] - ue.technical_title = user_extension_params["technical_title"] - ue.student_id = user_extension_params["student_id"] - ue.description = user_extension_params["description"] - ue.save! - u.save! - - sync_params = {} - - if (user_params["mail"] && user_params["mail"] != user_mail) - sync_params = sync_params.merge(email: user_params["mail"]) - end - - if sync_params.present? - interactor = Gitea::User::UpdateInteractor.call(u.login, sync_params) - if interactor.success? - render_ok - else - render_error(interactor.error) - end - end - end - end - rescue Exception => e - uid_logger_error(e.message) - tip_exception(-1, e.message) - end - - # 其他平台同步登录 - def remote_login - @user = User.try_to_login(params[:login], params[:password]) - if @user - successful_authentication(@user) - render_ok({user: {id: @user.id, token: @user.gitea_token}}) - else - render_error("用户不存在") - end - end - - #修改密码 - def remote_password - @user = User.find_by(login: params[:login]) - return render_error("未找到相关用户!") if @user.blank? - - sync_params = { - password: params[:password].to_s, - email: @user.mail - } - - interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) - if interactor.success? - @user.update_attribute(:password, params[:password]) - render_ok - else - render_error(interactor.error) - end - end - - - - # 用户注册 - # 注意:用户注册需要兼顾本地版,本地版是不需要验证码及激活码以及使用授权的,注册完成即可使用 - # params[:login] 邮箱或者手机号 - # params[:namespace] 登录名 - # params[:code] 验证码 - # code_type 1:注册手机验证码 8:邮箱注册验证码 - # 本地forge注册入口需要重新更改逻辑 - def register - # type只可能是1或者8 - user = nil - begin - Register::Form.new(register_params).validate! - - user = Users::RegisterService.call(register_params) - password = register_params[:password].strip - - # gitea用户注册, email, username, password - interactor = Gitea::RegisterInteractor.call({username: user.login, email: user.mail, password: password}) - if interactor.success? - gitea_user = interactor.result - result = Gitea::User::GenerateTokenService.call(user.login, password) - user.gitea_token = result['sha1'] - user.gitea_uid = gitea_user[:body]['id'] - if user.save! - UserExtension.create!(user_id: user.id) - successful_authentication(user) - render_ok - end - else - tip_exception(-1, interactor.error) - end - rescue Register::BaseForm::EmailError => e - render_result(-2, e.message) - rescue Register::BaseForm::LoginError => e - render_result(-3, e.message) - rescue Register::BaseForm::PhoneError => e - render_result(-4, e.message) - rescue Register::BaseForm::PasswordFormatError => e - render_result(-5, e.message) - rescue Register::BaseForm::PasswordConfirmationError => e - render_result(-7, e.message) - rescue Register::BaseForm::VerifiCodeError => e - render_result(-6, e.message) - rescue Exception => e - Gitea::User::DeleteService.call(user.login) unless user.nil? - uid_logger_error(e.message) - tip_exception(-1, e.message) - end - end - - # 用户登录 - def login - Users::LoginForm.new(login_params).validate! - @user = User.try_to_login(params[:login], params[:password]) - - return normal_status(-2, "错误的账号或密码") if @user.blank? - # user is already in local database - return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked? - - login_control = LimitForbidControl::UserLogin.new(@user) - return normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") if login_control.forbid? - - password_ok = @user.check_password?(params[:password].to_s) - unless password_ok - if login_control.remain_times-1 == 0 - normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") - else - normal_status(-2, "你已经输错密码#{login_control.error_times+1}次,还剩余#{login_control.remain_times-1}次机会") - end - login_control.increment! - return - end - - successful_authentication(@user) - sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) # TODO用户密码未同步 - - # session[:user_id] = @user.id - end - - def change_password - @user = User.find_by(login: params[:login]) - return render_error("未找到相关用户!") if @user.blank? - return render_error("旧密码不正确") unless @user.check_password?(params[:old_password]) - - sync_params = { - password: params[:password].to_s, - email: @user.mail - } - - interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) - if interactor.success? - @user.update_attribute(:password, params[:password]) - render_ok - else - render_error(interactor.error) - end - end - - # 忘记密码 - def reset_password - begin - Accounts::ResetPasswordForm.new(reset_password_params).validate! - - user = find_user - return render_error('未找到相关账号') if user.blank? - - user = Accounts::ResetPasswordService.call(user, reset_password_params) - LimitForbidControl::UserLogin.new(user).clear if user.save! - - render_ok - rescue Register::BaseForm::EmailError => e - render_result(-2, e.message) - rescue Register::BaseForm::PhoneError => e - render_result(-4, e.message) - rescue Register::BaseForm::PasswordFormatError => e - render_result(-5, e.message) - rescue Register::BaseForm::PasswordConfirmationError => e - render_result(-7, e.message) - rescue Register::BaseForm::VerifiCodeError => e - render_result(-6, e.message) - rescue ActiveRecord::Rollback => e - render_result(-1, "服务器异常") - rescue Exception => e - uid_logger_error(e.message) - tip_exception(e.message) - end - end - - def successful_authentication(user) - uid_logger("Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}") - # Valid user - self.logged_user = user - # generate a key and set cookie if autologin - - set_autologin_cookie(user) - UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id), :ip => request.remote_ip) - user.update_column(:last_login_on, Time.now) - session[:"#{default_yun_session}"] = user.id - Rails.logger.info("#########_____session_default_yun_session__________###############{default_yun_session}") - # 注册完成后有一天的试用申请(先去掉) - # UserDayCertification.create(user_id: user.id, status: 1) - end - - def set_autologin_cookie(user) - token = Token.get_or_create_permanent_login_token(user, "autologin") - sync_user_token_to_trustie(user.login, token.value) - - cookie_options = { - :value => token.value, - :expires => 1.month.from_now, - :path => '/', - :secure => false, - :httponly => true - } - if edu_setting('cookie_domain').present? - cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain')) - end - cookies[autologin_cookie_name] = cookie_options - cookies.signed[:user_id] ||= user.id - - logger.info("cookies is #{cookies} ======> #{cookies.signed[:user_id]} =====> #{cookies[autologin_cookie_name]}") - end - - def logout - Rails.logger.info("########___logout_current_user____________########{current_user.try(:id)}") - UserAction.create(action_id: User.current.id, action_type: "Logout", user_id: User.current.id, :ip => request.remote_ip) - logout_user - render :json => {status: 1, message: "退出成功!"} - end - - # 检验邮箱是否已被注册及邮箱或者手机号是否合法 - # 参数type为事件类型 1:注册;2:忘记密码;3:绑定 - def valid_email_and_phone - check_mail_and_phone_valid(params[:login], params[:type]) - end - - # 发送验证码 - # params[:login] 手机号或者邮箱号 - # params[:type]为事件通知类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验收手机号有效 # 如果有新的继续后面加 - # 发送验证码:send_type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 - # 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码 9: 验收手机号有效 - def get_verification_code - code = %W(0 1 2 3 4 5 6 7 8 9) - value = params[:login] - type = params[:type].strip.to_i - login_type = phone_mail_type(value) - send_type = verify_type(login_type, type) - verification_code = code.sample(6).join - - sign = Digest::MD5.hexdigest("#{OPENKEY}#{value}") - tip_exception(501, "请求不合理") if sign != params[:smscode] - - logger.info "########### 验证码:#{verification_code}" - logger.info("########get_verification_code: login_type: #{login_type}, send_type:#{send_type}, ") - - # 记录验证码 - check_verification_code(verification_code, send_type, value) - render_ok - end - - # check user's login or email or phone is used - # params[:value] 手机号或者邮箱号或者登录名 - # params[:type] 为事件类型 1:登录名(login) 2:email(邮箱) 3:phone(手机号) - def check - Register::CheckColumnsForm.new(check_params).validate! - render_ok - end - - private - - # type 事件类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加 - # login_type 1:手机类型 2:邮箱类型 - def verify_type login_type, type - case type - when 1 - login_type == 1 ? 1 : 8 - when 2 - login_type == 1 ? 2 : 3 - when 3 - login_type == 1 ? 4 : tip_exception('请填写正确的手机号') - when 4 - login_type == 1 ? tip_exception('请填写正确的邮箱') : 5 - when 5 - login_type == 1 ? 9 : tip_exception('请填写正确的手机号') - end - end - - def generate_login(login) - type = phone_mail_type(login.strip) - - if type == 1 - uid_logger("start register by phone: type is #{type}") - pre = 'p' - email = nil - phone = login - else - uid_logger("start register by email: type is #{type}") - pre = 'm' - email = login - phone = nil - end - code = generate_identifier User, 8, pre - - { login: pre + code, email: email, phone: phone } - end - - def user_params - params.require(:user).permit(:login, :email, :phone) - end - - def login_params - params.require(:account).permit(:login, :password) - end - - def check_params - params.permit(:type, :value) - end - - def register_params - params.permit(:login, :namespace, :password, :password_confirmation, :code) - end - - def reset_password_params - params.permit(:login, :password, :password_confirmation, :code) - end - - def find_user - phone_or_mail = strip(reset_password_params[:login]) - User.where("phone = :search OR mail = :search", search: phone_or_mail).last - end - -end +class AccountsController < ApplicationController + include ApplicationHelper + + def index + render json: session + end + + # 其他平台同步注册的用户 + def remote_register + Register::RemoteForm.new(remote_register_params).validate! + username = params[:username]&.gsub(/\s+/, "") + tip_exception("无法使用以下关键词:#{username},请重新命名") if ReversedKeyword.check_exists?(username) + email = params[:email]&.gsub(/\s+/, "") + password = params[:password] + platform = (params[:platform] || 'forge')&.gsub(/\s+/, "") + + ActiveRecord::Base.transaction do + result = autologin_register(username, email, password, platform) + if result[:message].blank? + render_ok({user: result[:user]}) + else + render_error(result[:message]) + end + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(-1, e.message) + end + + # 其他平台修改用户的信息,这边同步修改 + def remote_update + ActiveRecord::Base.transaction do + user_params = params[:user_params] + user_extension_params = params[:user_extension_params] + + u = User.find_by(login: params[:old_user_login]) + user_mail = u.try(:mail) + + if u.present? + ue = u.user_extension + u.login = user_params["login"] if user_params["login"] + u.mail = user_params["mail"] if user_params["mail"] + u.lastname = user_params["lastname"] if user_params["lastname"] + + ue.gender = user_extension_params["gender"] + ue.school_id = user_extension_params["school_id"] + ue.location = user_extension_params["location"] + ue.location_city = user_extension_params["location_city"] + ue.identity = user_extension_params["identity"] + ue.technical_title = user_extension_params["technical_title"] + ue.student_id = user_extension_params["student_id"] + ue.description = user_extension_params["description"] + ue.save! + u.save! + + sync_params = {} + + if (user_params["mail"] && user_params["mail"] != user_mail) + sync_params = sync_params.merge(email: user_params["mail"]) + end + + if sync_params.present? + interactor = Gitea::User::UpdateInteractor.call(u.login, sync_params) + if interactor.success? + render_ok + else + render_error(interactor.error) + end + end + end + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(-1, e.message) + end + + # 其他平台同步登录 + def remote_login + @user = User.try_to_login(params[:login], params[:password]) + if @user + successful_authentication(@user) + render_ok({user: {id: @user.id, token: @user.gitea_token}}) + else + render_error("用户不存在") + end + end + + #修改密码 + def remote_password + @user = User.find_by(login: params[:login]) + return render_error("未找到相关用户!") if @user.blank? + + sync_params = { + password: params[:password].to_s, + email: @user.mail + } + + interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) + if interactor.success? + @user.update_attribute(:password, params[:password]) + render_ok + else + render_error(interactor.error) + end + end + + + + # 用户注册 + # 注意:用户注册需要兼顾本地版,本地版是不需要验证码及激活码以及使用授权的,注册完成即可使用 + # params[:login] 邮箱或者手机号 + # params[:namespace] 登录名 + # params[:code] 验证码 + # code_type 1:注册手机验证码 8:邮箱注册验证码 + # 本地forge注册入口需要重新更改逻辑 + def register + # type只可能是1或者8 + user = nil + begin + Register::Form.new(register_params).validate! + + user = Users::RegisterService.call(register_params) + password = register_params[:password].strip + + # gitea用户注册, email, username, password + interactor = Gitea::RegisterInteractor.call({username: user.login, email: user.mail, password: password}) + if interactor.success? + gitea_user = interactor.result + result = Gitea::User::GenerateTokenService.call(user.login, password) + user.gitea_token = result['sha1'] + user.gitea_uid = gitea_user[:body]['id'] + if user.save! + UserExtension.create!(user_id: user.id) + successful_authentication(user) + render_ok + end + else + tip_exception(-1, interactor.error) + end + rescue Register::BaseForm::EmailError => e + render_result(-2, e.message) + rescue Register::BaseForm::LoginError => e + render_result(-3, e.message) + rescue Register::BaseForm::PhoneError => e + render_result(-4, e.message) + rescue Register::BaseForm::PasswordFormatError => e + render_result(-5, e.message) + rescue Register::BaseForm::PasswordConfirmationError => e + render_result(-7, e.message) + rescue Register::BaseForm::VerifiCodeError => e + render_result(-6, e.message) + rescue Exception => e + Gitea::User::DeleteService.call(user.login) unless user.nil? + uid_logger_error(e.message) + tip_exception(-1, e.message) + end + end + + # 用户登录 + def login + Users::LoginForm.new(login_params).validate! + @user = User.try_to_login(params[:login], params[:password]) + + return normal_status(-2, "错误的账号或密码") if @user.blank? + # user is already in local database + return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked? + + login_control = LimitForbidControl::UserLogin.new(@user) + return normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") if login_control.forbid? + + password_ok = @user.check_password?(params[:password].to_s) + unless password_ok + if login_control.remain_times-1 == 0 + normal_status(-2, "登录密码出错已达上限,账号已被锁定, 请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") + else + normal_status(-2, "你已经输错密码#{login_control.error_times+1}次,还剩余#{login_control.remain_times-1}次机会") + end + login_control.increment! + return + end + + successful_authentication(@user) + sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) # TODO用户密码未同步 + + # session[:user_id] = @user.id + end + + def change_password + @user = User.find_by(login: params[:login]) + return render_error("未找到相关用户!") if @user.blank? + return render_error("旧密码不正确") unless @user.check_password?(params[:old_password]) + + sync_params = { + password: params[:password].to_s, + email: @user.mail + } + + interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params) + if interactor.success? + @user.update_attribute(:password, params[:password]) + render_ok + else + render_error(interactor.error) + end + end + + # 忘记密码 + def reset_password + begin + Accounts::ResetPasswordForm.new(reset_password_params).validate! + + user = find_user + return render_error('未找到相关账号') if user.blank? + + user = Accounts::ResetPasswordService.call(user, reset_password_params) + LimitForbidControl::UserLogin.new(user).clear if user.save! + + render_ok + rescue Register::BaseForm::EmailError => e + render_result(-2, e.message) + rescue Register::BaseForm::PhoneError => e + render_result(-4, e.message) + rescue Register::BaseForm::PasswordFormatError => e + render_result(-5, e.message) + rescue Register::BaseForm::PasswordConfirmationError => e + render_result(-7, e.message) + rescue Register::BaseForm::VerifiCodeError => e + render_result(-6, e.message) + rescue ActiveRecord::Rollback => e + render_result(-1, "服务器异常") + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + end + + def successful_authentication(user) + uid_logger("Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}") + # Valid user + self.logged_user = user + # generate a key and set cookie if autologin + + set_autologin_cookie(user) + UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id), :ip => request.remote_ip) + user.update_column(:last_login_on, Time.now) + session[:"#{default_yun_session}"] = user.id + Rails.logger.info("#########_____session_default_yun_session__________###############{default_yun_session}") + # 注册完成后有一天的试用申请(先去掉) + # UserDayCertification.create(user_id: user.id, status: 1) + end + + def set_autologin_cookie(user) + token = Token.get_or_create_permanent_login_token(user, "autologin") + # sync_user_token_to_trustie(user.login, token.value) + + cookie_options = { + :value => token.value, + :expires => 1.month.from_now, + :path => '/', + :secure => false, + :httponly => true + } + if edu_setting('cookie_domain').present? + cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain')) + end + cookies[autologin_cookie_name] = cookie_options + cookies.signed[:user_id] ||= user.id + + logger.info("cookies is #{cookies} ======> #{cookies.signed[:user_id]} =====> #{cookies[autologin_cookie_name]}") + end + + def logout + Rails.logger.info("########___logout_current_user____________########{current_user.try(:id)}") + UserAction.create(action_id: User.current.id, action_type: "Logout", user_id: User.current.id, :ip => request.remote_ip) + logout_user + render :json => {status: 1, message: "退出成功!"} + end + + # 检验邮箱是否已被注册及邮箱或者手机号是否合法 + # 参数type为事件类型 1:注册;2:忘记密码;3:绑定 + def valid_email_and_phone + check_mail_and_phone_valid(params[:login], params[:type]) + end + + # 发送验证码 + # params[:login] 手机号或者邮箱号 + # params[:type]为事件通知类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验收手机号有效 # 如果有新的继续后面加 + # 发送验证码:send_type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 + # 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码 9: 验收手机号有效 + def get_verification_code + code = %W(0 1 2 3 4 5 6 7 8 9) + value = params[:login] + type = params[:type].strip.to_i + login_type = phone_mail_type(value) + send_type = verify_type(login_type, type) + verification_code = code.sample(6).join + + sign = Digest::MD5.hexdigest("#{OPENKEY}#{value}") + tip_exception(501, "请求不合理") if sign != params[:smscode] + + logger.info "########### 验证码:#{verification_code}" + logger.info("########get_verification_code: login_type: #{login_type}, send_type:#{send_type}, ") + + # 记录验证码 + check_verification_code(verification_code, send_type, value) + render_ok + end + + # check user's login or email or phone is used + # params[:value] 手机号或者邮箱号或者登录名 + # params[:type] 为事件类型 1:登录名(login) 2:email(邮箱) 3:phone(手机号) + def check + Register::CheckColumnsForm.new(check_params).validate! + render_ok + end + + private + + # type 事件类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加 + # login_type 1:手机类型 2:邮箱类型 + def verify_type login_type, type + case type + when 1 + login_type == 1 ? 1 : 8 + when 2 + login_type == 1 ? 2 : 3 + when 3 + login_type == 1 ? 4 : tip_exception('请填写正确的手机号') + when 4 + login_type == 1 ? tip_exception('请填写正确的邮箱') : 5 + when 5 + login_type == 1 ? 9 : tip_exception('请填写正确的手机号') + end + end + + def generate_login(login) + type = phone_mail_type(login.strip) + + if type == 1 + uid_logger("start register by phone: type is #{type}") + pre = 'p' + email = nil + phone = login + else + uid_logger("start register by email: type is #{type}") + pre = 'm' + email = login + phone = nil + end + code = generate_identifier User, 8, pre + + { login: pre + code, email: email, phone: phone } + end + + def user_params + params.require(:user).permit(:login, :email, :phone) + end + + def login_params + params.require(:account).permit(:login, :password) + end + + def check_params + params.permit(:type, :value) + end + + def register_params + params.permit(:login, :namespace, :password, :password_confirmation, :code) + end + + def reset_password_params + params.permit(:login, :password, :password_confirmation, :code) + end + + def find_user + phone_or_mail = strip(reset_password_params[:login]) + User.where("phone = :search OR mail = :search", search: phone_or_mail).last + end + + def remote_register_params + params.permit(:username, :email, :password, :platform) + end + +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0eaf703e..4dcf59d1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -272,18 +272,18 @@ class ApplicationController < ActionController::Base # 测试版前端需求 logger.info("subdomain:#{request.subdomain}") - # if request.subdomain != "www" - # if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除 - # User.current = User.find 81403 - # elsif params[:debug] == 'student' - # User.current = User.find 8686 - # elsif params[:debug] == 'admin' - # logger.info "@@@@@@@@@@@@@@@@@@@@@@ debug mode....." - # user = User.find 36480 - # User.current = user - # cookies.signed[:user_id] = user.id - # end - # end + if request.subdomain != "www" + if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除 + User.current = User.find 81403 + elsif params[:debug] == 'student' + User.current = User.find 8686 + elsif params[:debug] == 'admin' + logger.info "@@@@@@@@@@@@@@@@@@@@@@ debug mode....." + user = User.find 36480 + User.current = user + cookies.signed[:user_id] = user.id + end + end # User.current = User.find 81403 end @@ -704,14 +704,20 @@ class ApplicationController < ActionController::Base Rails.application.config_for(:configuration)['platform_url'] || request.base_url 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 convert_image! @image = params[:image] @image = @image.nil? && params[:user].present? ? params[:user][:image] : @image return unless @image.present? max_size = EduSetting.get('upload_avatar_max_size') || 2 * 1024 * 1024 # 2M if @image.class == ActionDispatch::Http::UploadedFile - render_error('请上传文件') if @image.size.zero? - render_error('文件大小超过限制') if @image.size > max_size.to_i + return render_error('请上传文件') if @image.size.zero? + return render_error('文件大小超过限制') if @image.size > max_size.to_i + return render_error('头像格式不正确!') unless image_type?(File.extname(@image.original_filename.to_s)[1..-1]) else image = @image.to_s.strip return render_error('请上传正确的图片') if image.blank? diff --git a/app/controllers/claims_controller.rb b/app/controllers/claims_controller.rb new file mode 100644 index 00000000..d9959baf --- /dev/null +++ b/app/controllers/claims_controller.rb @@ -0,0 +1,138 @@ +#coding=utf-8 +class ClaimsController < ApplicationController + # skip_before_action :verify_authenticity_token + protect_from_forgery with: :null_session + before_action :require_login, except: [:index] + before_action :set_issue + + def index + @user_claimed = 0 + @claims = @issue.claims.claim_includes.order("created_at desc") + + @claims.each do |claim| + if claim.user_id == current_user.id + @user_claimed = 1 + break + end + end + render file: 'app/views/claims/list.json.jbuilder' + end + + def create + @claim = Claim.find_by_sql(["select id from claims where issue_id=? and user_id=?",params[:issue_id], current_user.id]) + if @claim.present? + return normal_status(-1,"您已经声明过该易修") + end + + ActiveRecord::Base.transaction do + @claim = Claim.new(parse_issue_params(params)) + if @claim.save + @claims = @issue.claims.claim_includes.order("created_at desc") + @user_claimed = 1 + + journal_params = { + journalized_id: params[:issue_id], + journalized_type: "Issue", + user_id: current_user.id , + notes: "新建声明: #{params[:claim_note]}", + } + + journal = Journal.new(journal_params) + if journal.save + render file: 'app/views/claims/list.json.jbuilder' + else + normal_status(-1,"新建声明关联评论操作失败") + end + + else + normal_status(-1,"新建声明操作失败") + end + end + end + + def update + @claim = Claim.find_by_id(params[:claim_id]) + if @claim.blank? + return normal_status(-1,"易修不存在") + end + + if @claim.user_id != current_user.id + return normal_status(-1,"你不能更新别人的声明") + end + + ActiveRecord::Base.transaction do + if @claim.update_attribute(:note,params[:claim_note]) + @claims = @issue.claims.claim_includes.order("created_at desc") + @user_claimed = 1 + + journal_params = { + journalized_id: params[:issue_id], + journalized_type: "Issue", + user_id: current_user.id , + notes: "更新声明: #{params[:claim_note]}", + } + + journal = Journal.new(journal_params) + if journal.save + render file: 'app/views/claims/list.json.jbuilder' + else + normal_status(-1,"新建声明关联评论操作失败") + end + + else + normal_status(-1,"声明更新操作失败") + end + end + end + + def destroy + @claim = Claim.find_by_sql(["select id from claims where issue_id=? and user_id=?",params[:issue_id], current_user.id]) + if @claim.blank? + normal_status(-1,"您未曾声明过该易修") + else + @claim = @claim[0] + # 判断current user是否是claimer + ActiveRecord::Base.transaction do + if @claim.destroy + + @claims = @issue.claims.claim_includes.order("created_at desc") + @user_claimed = 0 + + journal_params = { + journalized_id: params[:issue_id], + journalized_type: "Issue", + user_id: current_user.id , + notes: "取消声明", + } + + journal = Journal.new(journal_params) + if journal.save + render file: 'app/views/claims/list.json.jbuilder' + else + normal_status(-1,"新建声明关联评论操作失败") + end + + else + normal_status(-1,"取消声明操作失败") + end + end + end + end + + private + def parse_issue_params(params) + { + issue_id: params[:issue_id], + user_id: current_user.id, + note: params[:claim_note], + } + end + + def set_issue + @issue = Issue.find_by_id(params[:issue_id]) + unless @issue.present? + normal_status(-1, "易修不存在") + end + end + +end \ No newline at end of file diff --git a/app/controllers/compare_controller.rb b/app/controllers/compare_controller.rb index c8918d1c..07691d79 100644 --- a/app/controllers/compare_controller.rb +++ b/app/controllers/compare_controller.rb @@ -42,12 +42,14 @@ class CompareController < ApplicationController end def load_compare_params - @base = Addressable::URI.unescape(params[:base]) + # @base = Addressable::URI.unescape(params[:base]) + @base = params[:base].include?(":") ? Addressable::URI.unescape(params[:base].split(":")[0]) + ':' + Base64.decode64(params[:base].split(":")[1]) : Base64.decode64(params[:base]) @head = params[:head].include?('.json') ? params[:head][0..-6] : params[:head] - + # @head = Addressable::URI.unescape(@head) + @head = @head.include?(":") ? Addressable::URI.unescape(@head.split(":")[0]) + ':' + Base64.decode64(@head.split(":")[1]) : Base64.decode64(@head) end def gitea_compare(base, head) - Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, base, head, current_user.gitea_token) + Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, CGI.escape(base), CGI.escape(head), current_user.gitea_token) end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 6ea8fb8e..73b3ee4c 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -486,7 +486,8 @@ class IssuesController < ApplicationController end def operate_issue_permission - return render_forbidden("您没有权限进行此操作.") unless current_user.present? && current_user.logged? && (current_user.admin? || @project.member?(current_user) || @project.is_public?) + @issue = Issue.find_by_id(params[:id]) unless @issue.present? + return render_forbidden("您没有权限进行此操作.") unless current_user.present? && current_user.logged? && (current_user.admin? || @project.member?(current_user) || (@project.is_public && @issue.nil?) || (@project.is_public && @issue.present? && @issue.author_id == current_user.id)) end def export_issues(issues) diff --git a/app/controllers/organizations/organizations_controller.rb b/app/controllers/organizations/organizations_controller.rb index bf011e5f..58aa0d29 100644 --- a/app/controllers/organizations/organizations_controller.rb +++ b/app/controllers/organizations/organizations_controller.rb @@ -28,7 +28,7 @@ class Organizations::OrganizationsController < Organizations::BaseController def create ActiveRecord::Base.transaction do tip_exception("无法使用以下关键词:#{organization_params[:name]},请重新命名") if ReversedKeyword.check_exists?(organization_params[:name]) - Organizations::CreateForm.new(organization_params).validate! + Organizations::CreateForm.new(organization_params.merge(original_name: "")).validate! @organization = Organizations::CreateService.call(current_user, organization_params) Util.write_file(@image, avatar_path(@organization)) if params[:image].present? end @@ -39,7 +39,7 @@ class Organizations::OrganizationsController < Organizations::BaseController def update ActiveRecord::Base.transaction do - Organizations::CreateForm.new(organization_params).validate! + Organizations::CreateForm.new(organization_params.merge(original_name: @organization.login)).validate! login = @organization.login @organization.login = organization_params[:name] if organization_params[:name].present? @organization.nickname = organization_params[:nickname] if organization_params[:nickname].present? diff --git a/app/controllers/organizations/team_users_controller.rb b/app/controllers/organizations/team_users_controller.rb index 0a5ff28a..7a71dbba 100644 --- a/app/controllers/organizations/team_users_controller.rb +++ b/app/controllers/organizations/team_users_controller.rb @@ -18,7 +18,7 @@ class Organizations::TeamUsersController < Organizations::BaseController ActiveRecord::Base.transaction do @team_user = TeamUser.build(@organization.id, @operate_user.id, @team.id) @organization_user = OrganizationUser.build(@organization.id, @operate_user.id) - SendTemplateMessageJob.perform_later('OrganizationRole', @operate_user.id, @organization.id, @team.authorize_name) if Site.has_notice_menu? + SendTemplateMessageJob.perform_later('TeamJoined', @operate_user.id, @organization.id, @team.id) if Site.has_notice_menu? Gitea::Organization::TeamUser::CreateService.call(@organization.gitea_token, @team.gtid, @operate_user.login) end rescue Exception => e @@ -31,6 +31,7 @@ class Organizations::TeamUsersController < Organizations::BaseController ActiveRecord::Base.transaction do @team_user.destroy! Gitea::Organization::TeamUser::DeleteService.call(@organization.gitea_token, @team.gtid, @operate_user.login) + SendTemplateMessageJob.perform_later('TeamLeft', @operate_user.id, @organization.id, @team.id) if Site.has_notice_menu? org_team_users = @organization.team_users.where(user_id: @operate_user.id) unless org_team_users.present? @organization.organization_users.find_by(user_id: @operate_user.id).destroy! diff --git a/app/controllers/organizations/teams_controller.rb b/app/controllers/organizations/teams_controller.rb index c599ac42..285f9960 100644 --- a/app/controllers/organizations/teams_controller.rb +++ b/app/controllers/organizations/teams_controller.rb @@ -4,15 +4,24 @@ class Organizations::TeamsController < Organizations::BaseController before_action :check_user_can_edit_org, only: [:create, :update, :destroy] def index - #if @organization.is_owner?(current_user) || current_user.admin? - @teams = @organization.teams - #else - # @teams = @organization.teams.joins(:team_users).where(team_users: {user_id: current_user.id}) - #end - @is_admin = can_edit_org? - @teams = @teams.includes(:team_units, :team_users) - @teams = kaminari_paginate(@teams) + if params[:is_full].present? + if can_edit_org? + @teams = @organization.teams + else + @teams = [] + end + else + #if @organization.is_owner?(current_user) || current_user.admin? + @teams = @organization.teams + #else + # @teams = @organization.teams.joins(:team_users).where(team_users: {user_id: current_user.id}) + #end + @is_admin = can_edit_org? + @teams = @teams.includes(:team_units, :team_users) + + @teams = kaminari_paginate(@teams) + end end def search @@ -34,8 +43,12 @@ class Organizations::TeamsController < Organizations::BaseController def create ActiveRecord::Base.transaction do - Organizations::CreateTeamForm.new(team_params).validate! - @team = Organizations::Teams::CreateService.call(current_user, @organization, team_params) + if @organization.teams.count >= 50 + return render_forbidden("组织的团队数量已超过限制!") + else + Organizations::CreateTeamForm.new(team_params).validate! + @team = Organizations::Teams::CreateService.call(current_user, @organization, team_params) + end end rescue Exception => e uid_logger_error(e.message) diff --git a/app/controllers/project_trends_controller.rb b/app/controllers/project_trends_controller.rb index 66702608..f283d05f 100644 --- a/app/controllers/project_trends_controller.rb +++ b/app/controllers/project_trends_controller.rb @@ -3,7 +3,7 @@ class ProjectTrendsController < ApplicationController before_action :check_project_public def index - project_trends = @project.project_trends.preload(:user, trend: :user) + project_trends = @project.project_trends.preload(:user, trend: :user, project: :owner) check_time = params[:time] #时间的筛选 check_type = params[:type] #动态类型的筛选,目前已知的有 Issue, PullRequest, Version diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 1864c696..57180532 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -16,13 +16,13 @@ class ProjectsController < ApplicationController menu.append(menu_hash_by_name("home")) menu.append(menu_hash_by_name("code")) if @project.has_menu_permission("code") menu.append(menu_hash_by_name("issues")) if @project.has_menu_permission("issues") - menu.append(menu_hash_by_name("pulls")) if @project.has_menu_permission("pulls") - menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") - menu.append(menu_hash_by_name("devops")) if @project.has_menu_permission("devops") + menu.append(menu_hash_by_name("pulls")) if @project.has_menu_permission("pulls") && @project.forge? + menu.append(menu_hash_by_name("devops")) if @project.has_menu_permission("devops") && @project.forge? menu.append(menu_hash_by_name("versions")) if @project.has_menu_permission("versions") - menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") + menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") && @project.forge? + menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") && @project.forge? menu.append(menu_hash_by_name("activity")) - menu.append(menu_hash_by_name("settings")) if current_user.admin? || @project.manager?(current_user) + menu.append(menu_hash_by_name("settings")) if (current_user.admin? || @project.manager?(current_user)) && @project.forge? render json: menu end @@ -82,8 +82,9 @@ class ProjectsController < ApplicationController def branches return @branches = [] unless @project.forge? - result = Gitea::Repository::Branches::ListService.call(@owner, @project.identifier) - @branches = result.is_a?(Hash) && result.key?(:status) ? [] : result + # result = Gitea::Repository::Branches::ListService.call(@owner, @project.identifier) + result = Gitea::Repository::Branches::ListNameService.call(@owner, @project.identifier) + @branches = result.is_a?(Hash) ? (result.key?(:status) ? [] : result["branch_name"]) : result end def branches_slice diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index 9cb6e5e2..bd9ae770 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -56,7 +56,7 @@ class PullRequestsController < ApplicationController end def create - return normal_status(-1, "您不是目标分支开发者,没有权限,请联系目标分支作者.") unless @project.operator?(current_user) + # return normal_status(-1, "您不是目标分支开发者,没有权限,请联系目标分支作者.") unless @project.operator?(current_user) ActiveRecord::Base.transaction do @pull_request, @gitea_pull_request = PullRequests::CreateService.call(current_user, @owner, @project, params) if @gitea_pull_request[:status] == :success @@ -89,6 +89,7 @@ class PullRequestsController < ApplicationController else ActiveRecord::Base.transaction do begin + return normal_status(-1, "title不能超过255个字符") if params[:title].length > 255 merge_params @issue&.issue_tags_relates&.destroy_all if params[:issue_tag_ids].blank? diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 2d96725e..5ebc00b3 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -1,368 +1,387 @@ -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 - @sub_entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder&.repo_name, {path: file_path_uri}) - 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? - @hash_commit = nil - 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? - @commit = {} - @commit_diff ={} - 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? - @contributors = [] - else - @contributors = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier) - end - 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 - render json: languages_precentagable - end - - def archive - domain = Gitea.gitea_config[:domain] - api_url = Gitea.gitea_config[:base_url] - archive_url = "/repos/#{@owner.login}/#{@repository.identifier}/archive/#{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/#{params[:filepath]}?ref=#{params[:ref]}" - file_path = [domain, api_url, url].join - file_path = [file_path, "access_token=#{current_user&.gitea_token}"].join("&") if @repository.hidden? - - redirect_to URI.escape(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(gitea_number: gitea_request["body"]["number"], 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 + @contributors = Gitea::Repository::Contributors::GetService.call(@owner, @repository.identifier) + end + 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 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c20bb6ea..5af634f1 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -6,7 +6,7 @@ class UsersController < ApplicationController before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard] before_action :require_login, only: %i[me list sync_user_info] before_action :connect_to_ci_db, only: [:get_user_info] - before_action :convert_image!, only: [:update] + before_action :convert_image!, only: [:update, :update_image] skip_before_action :check_sign, only: [:attachment_show] def connect_to_ci_db(options={}) @@ -82,10 +82,21 @@ class UsersController < ApplicationController Util.write_file(@image, avatar_path(@user)) if user_params[:image].present? @user.attributes = user_params.except(:image) unless @user.save - render_error(@user.errors.full_messages.join(", ")) + render_error(-1, @user.errors.full_messages.join(", ")) end end + def update_image + return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) + return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) + + Util.write_file(@image, avatar_path(@user)) + return render_ok({message: '头像修改成功'}) + rescue Exception => e + uid_logger_error(e.message) + render_error(-1, '头像修改失败!') + end + def me @user = current_user end diff --git a/app/docs/slate/source/includes/_projects.md b/app/docs/slate/source/includes/_projects.md new file mode 100644 index 00000000..d4899a71 --- /dev/null +++ b/app/docs/slate/source/includes/_projects.md @@ -0,0 +1,852 @@ +# Projects + +## 申请加入项目 +申请加入项目 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/applied_projects.json +``` + +```javascript +await octokit.request('POST /api/appliedr_projects.json') +``` + +### HTTP 请求 +`POST /api/applied_projects.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|applied_project.code |是| |string |邀请码 | +|applied_project.role |否| |string |项目权限,reporter: 报告者, developer: 开发者,manager:管理员 | + +> 请求的JSON示例 + +```json +{ + "applied_project": { + "code": "1una34", + "role": "developer" + } +} +``` + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |申请id | +|status |string |申请状态,canceled:取消,common:正在申请, accept:已接受,refuse:已拒绝| +|time_ago |string |项目申请创建的时间 | +|project.id |int |申请项目的id | +|project.identifier |string |申请项目的标识 | +|project.name |string |申请项目的名称 | +|project.description |string |申请项目的描述 | +|project.is_public |bool |申请项目是否公开 | +|project.owner.id |bool |申请项目拥有者id | +|project.owner.type |string |申请项目拥有者类型 | +|project.owner.name |string |申请项目拥有者昵称 | +|project.owner.login |string |申请项目拥有者标识 | +|project.owner.image_url |string |申请项目拥有者头像 | +|user.id |int |申请创建者的id | +|user.type |string |申请创建者的类型 | +|user.name |string |申请创建者的名称 | +|user.login |string |申请创建者的标识 | +|user.image_url |string |申请创建者头像 | +> 返回的JSON示例: + +```json +{ + "project": { + "id": 74, + "identifier": "hehuisssjssjjsjs", + "name": "hehuisssjssjjsjs", + "description": "wwww", + "is_public": false, + "owner": { + "id": 10, + "type": "User", + "name": "testforge1", + "login": "testforge1", + "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "何慧", + "login": "yystopf", + "image_url": "images/avatars/User/6?t=1622513134" + }, + "id": 7, + "status": "common", + "created_at": "2021-06-09 16:41", + "time_ago": "1分钟前" +} +``` + +## 获取项目列表 +获取项目列表,也可以更加相关条件过滤搜素 + +> 示例: + +```shell +curl -X GET \ +-d "page=1" \ +-d "limit=5" \ +http://localhost:3000/api/projects | jq +``` + +```javascript +await octokit.request('GET /api/projects') +``` + +### HTTP 请求 +`GET api/projects` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +page | false | 1 | string | 页数,第几页 +limit | false | 15 | string | 每页多少条数据,默认15条 +sort_by | false | | string | 排序类型, 取值:updated_on、created_on、forked_count、praises_count; updated_on: 更新时间排序,created_on: 创建时间排序,forked_count: fork数据排序,praises_count: 点赞数量排序,默认为updated_on更新时间排序 +sort_direction| false | | string | 排序方式,取值为: desc、asc; desc: 降序排序, asc: 升序排序, 默认为:desc +search | false | | string | 按照项目名称搜索 +category_id | false | | int | 项目类别id +language_id | false | | int | 项目语言id +project_type | false | | string | 项目类型, 取值为:common、mirror; common:开源托管项目, mirror:开源镜像项目 + + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +total_count | int | 项目总条数 +id | string | 项目id +name | string | 项目名称 +description | string | 项目简介 +visits | int | 流量数 +forked_count | int | 被fork的数量 +praises_count | int | star数量 +is_public | boolean | 是否公开, true:公开,false:未公开 +mirror_url | string | 镜像url +last_update_time| int | 最后更新时间,为UNIX格式的时间戳 +author | object | 项目创建者 +-- name | string | 用户名,也是用户标识 +category | object | 项目类别 +-- id | int | 项目类型id +-- name | string | 项目类型名称 +language | object | 项目语言 +-- id | int | 项目语言id +-- name | string | 项目语言名称 + + +> 返回的JSON示例: + +```json +{ + "total_count": 3096, + "projects": [ + { + "id": 1400794, + "repo_id": 1402452, + "identifier": "cscw_2021_sponsor", + "name": "Sponsor机制下的开源贡献", + "description": "CSCW 2021 sponsor机制研究", + "visits": 5, + "praises_count": 0, + "forked_count": 0, + "is_public": true, + "mirror_url": null, + "type": 0, + "last_update_time": 1611971671, + "time_ago": "2天前", + "forked_from_project_id": null, + "open_devops": false, + "platform": "forge", + "author": { + "name": "张迅晖", + "login": "Nigel", + "image_url": "images/avatars/User/3675?t=1611832880" + }, + "category": { + "id": 13, + "name": "云计算和大数据" + }, + "language": { + "id": 34, + "name": "Python3.6" + } + } + ] +} +``` + + + +## 推荐项目 +获取推荐项目列表 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/projects/recommend | jq +``` + +```javascript +await octokit.request('GET /api/projects/recommend.json') +``` + +### HTTP 请求 +`GET api/projects/recommend` + + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +total_count |int |项目总条数 +id |string |项目id +name |string|项目名称 +description |string|项目简介 +visits |int|流量数 +forked_count |int|被fork的数量 +praises_count |int|star数量 +is_public |boolean|是否公开, true:公开,false:未公开 +mirror_url |string|镜像url +last_update_time|int|最后更新时间,为UNIX格式的时间戳 +author |object|项目创建者 +-- name |string|用户名,也是用户标识 +category |object|项目类别 +-- id |int|项目类型id +-- name |string|项目类型名称 +language |object|项目语言 +-- id |int|项目语言id +-- name |string|项目语言名称 + + +> 返回的JSON示例: + +```json +[ + { + "id": 20, + "repo_id": 2, + "identifier": "PNAekinmH", + "name": "FNILL", + "visits": 13567, + "author": { + "name": "王一达", + "login": "wangyida", + "image_url": "avatars/User/b" + }, + "category": { + "id": 8, + "name": "其他" + } + } +] +``` + + + +## 项目导航 +获取项目导航信息 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/yystopf/ceshi/menu_list | jq +``` + +```javascript +await octokit.request('GET /api/yystopf/ceshi/menu_list') +``` + +### HTTP 请求 +`GET api/:owner/:repo/menu_list` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +owner |是| |string |用户登录名 +repo |是| |string |项目标识identifier + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +menu_name |string|导航名称, home:主页,code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑,activity:动态,setting:仓库设置 + + +> 返回的JSON示例: + +```json +[ + { + "menu_name": "home" + }, + { + "menu_name": "code" + }, + { + "menu_name": "pulls" + }, + { + "menu_name": "activity" + } +] +``` + + +## 项目主页 +获取项目主页信息 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/jasder/forgeplus/about | jq +``` + +```javascript +await octokit.request('GET /api/jasder/forgeplus/about') +``` + +### HTTP 请求 +`GET api/:owner/:repo/about` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +owner |是| |string |用户登录名 +repo |是| |string |项目标识identifier + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +identifier |string|project's identifier +content |string|主页内容 +attachments |array|附件 + + +> 返回的JSON示例: + +```json +{ + "content": "", + "identifier": "forgeplus", + attachments: [] +} +``` + + +## 项目模块信息 +项目模块信息 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/yystopf/ceshi/project_units.json +``` + +```javascript +await octokit.request('GET /api/yystopf/ceshi/project_units') +``` + +### HTTP 请求 +`GET /api/yystopf/ceshi/project_units` + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|type |string|模块名称 | + +> 返回的JSON示例: + +```json +[ + { + "type": "code" + }, + { + "type": "pulls" + }, + { + "type": "issues" + } +] +``` + +## 更改项目模块展示 +更改项目模块展示 + +> 示例: + +```shell +curl -X POST \ +-H "accept: application/json" \ +-H "Content-Type: application/json" \ +-d "{ \"unit_typs\": [\"code\", \"pulls\"]}" \ +http://localhost:3000/api/yystopf/ceshi/project_units.json +``` + +```javascript +await octokit.request('POST /api/yystopf/ceshi/project_units') +``` + +### HTTP 请求 +`POST /api/yystopf/ceshi/project_units` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|unit_types |是| |array | 项目模块内容, 支持以下参数:code:代码库,issues:疑修,pulls:合并请求,devops:工作流,versions:里程碑 | + + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|status |int|返回状态, 0: 表示操作成功 | +|message |string|返回信息说明| + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} +``` + +## 创建项目 +创建项目 + +> 示例: + +```shell +curl -X POST \ +-d "user_id=36401" \ +-d "name=hnfl_demo" \ +-d "description=my first project" \ +-d "repository_name=hnfl_demo" \ +-d "project_category_id=1" \ +-d "project_language_id=2" \ +-d "ignore_id=2" \ +-d "license_id=1" \ +http://localhost:3000/api/projects.json +``` + +```javascript +await octokit.request('GET /api/projects.json') +``` + +### HTTP 请求 +`POST api/projects` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|user_id |是| |int |用户id或者组织id | +|name |是| |string |项目名称 | +|description |是| |string |项目描述 | +|repository_name |是| |string |仓库名称, 只含有数字、字母、下划线不能以下划线开头和结尾,且唯一 | +|project_category_id|是| |int |项目类别id | +|project_language_id|是| |int |项目语言id | +|ignore_id |否| |int |gitignore相关id | +|license_id |否| |int |开源许可证id | +|private |否| |boolean|项目是否私有, true:为私有,false: 公开,默认为公开 | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |id | +|name |string|项目名称| + + +> 返回的JSON示例: + +```json +{ + "id": 3240, + "name": "好项目" +} +``` + + +## 创建镜像项目 +创建镜像项目 + +> 示例: + +```shell +curl -X POST \ +-d "user_id=36408" \ +-d "clone_addr=https://gitea.com/mx8090alex/golden.git" \ +-d "name=golden_mirror1" \ +-d "description=golden_mirror" \ +-d "project_category_id=1" \ +-d "project_language_id=2" \ +http://localhost:3000/api/projects/migrate.json +``` + +```javascript +await octokit.request('GET /api/projects/migrate.json') +``` + +### HTTP 请求 +`POST api/projects/migrate.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|user_id |是| |int |用户id或者组织id | +|name |是| |string |项目名称 | +|clone_addr |是| |string |镜像项目clone地址 | +|description |否| |string |项目描述 | +|repository_name |是| |string |仓库名称, 只含有数字、字母、下划线不能以下划线开头和结尾,且唯一 | +|project_category_id|是| |int |项目类别id | +|project_language_id|是| |int |项目语言id | +|is_mirror |否| |boolean|是否设置为镜像, true:是, false:否,默认为否 | +|auth_username |否| |string|镜像源仓库的登录用户名 | +|auth_password |否| |string|镜像源仓库的登录秘密 | +|private |否| |boolean|项目是否私有, true:为私有,false: 非私有,默认为公开 | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |id | +|name |string|项目名称| + + +> 返回的JSON示例: + +```json +{ + "id": 3241, + "name": "这是一个镜像项目" +} +``` + + +## 同步镜像 +手动同步镜像 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/repositories/1244/sync_mirror.json +``` + +```javascript +await octokit.request('POST /api/repositories/1244/sync_mirror.json') +``` + +### HTTP 请求 +`POST api/repositories/:id/sync_mirror.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|id |是| |int |仓库id | + + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|status |int |状态码, 0:标识请求成功 | +|message |string|服务端返回的信息说明| + + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} +``` + + +## Fork项目 +fork项目 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/jasder/forgeplus/forks.json +``` + +```javascript +await octokit.request('POST /api/jaser/jasder_test/forks.json') +``` + +### HTTP 请求 +`POST api/:owner/:repo/forks.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |项目id | +|identifier |string|项目标识| + + +> 返回的JSON示例: + +```json +{ + "id": 3290, + "identifier": "newadm" +} +``` + +## 用户管理的组织列表 +用户管理的组织列表 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizations.json | jq +``` + +```javascript +await octokit.request('GET /api/:owner/:repo/applied_transfer_projects/organizations') +``` + +### HTTP 请求 +`GET api/:owner/:repo/applied_transfer_projects/organizations` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +owner |是| |string |用户登录名 +repo |是| |string |项目标识identifier + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +name |string|组织标识 +nickname |string|组织名称 +description|string|组织描述 +avatar_url|string|组织头像 + + +> 返回的JSON示例: + +```json +{ + "total_count": 3, + "organizations": [ + { + "id": 9, + "name": "ceshi_org", + "nickname": "测试组织", + "description": "测试组织", + "avatar_url": "images/avatars/Organization/9?t=1612706073" + }, + { + "id": 51, + "name": "ceshi", + "nickname": "测试组织哈哈哈", + "description": "23212312", + "avatar_url": "images/avatars/Organization/51?t=1618800723" + }, + { + "id": 52, + "name": "ceshi1", + "nickname": "身份卡手动阀", + "description": "1231手动阀是的", + "avatar_url": "images/avatars/Organization/52?t=1618805056" + } + ] +} +``` + +## 迁移项目 +迁移项目,edit接口is_transfering为true表示正在迁移 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects.json +``` + +```javascript +await octokit.request('POST /api/:owner/:repo/applied_transfer_projects.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/applied_transfer_projects.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | +|owner_name|是| |string |迁移对象标识 | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |项目id | +|status |string |项目迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |项目迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | +> 返回的JSON示例: + +```json +{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "1分钟前" +} +``` + +## 取消迁移项目 +迁移项目,edit接口is_transfering为true表示正在迁移 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/cancel.json +``` + +```javascript +await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/applied_transfer_projects/cancel.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |迁移id | +|status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | +> 返回的JSON示例: + +```json +{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "1分钟前" +} +``` \ No newline at end of file diff --git a/app/docs/slate/source/includes/_repositories.md b/app/docs/slate/source/includes/_repositories.md index 3604689c..a44d2b35 100644 --- a/app/docs/slate/source/includes/_repositories.md +++ b/app/docs/slate/source/includes/_repositories.md @@ -1240,11 +1240,11 @@ await octokit.request('GET /api/yystopf/ceshi/webhooks/3/edit.json') |delete|分支或标签删除| |fork|仓库被fork| |push|git仓库推送| -|issue|易修已打开、已关闭、已重新打开或编辑| -|issue_assign|易修被指派| -|issue_label|易修标签被更新或删除| -|issue_milestone|易修被收入里程碑| -|issue_comment|易修评论| +|issue|疑修已打开、已关闭、已重新打开或编辑| +|issue_assign|疑修被指派| +|issue_label|疑修标签被更新或删除| +|issue_milestone|疑修被收入里程碑| +|issue_comment|疑修评论| |pull_request|合并请求| |pull_request_assign|合并请求被指派| |pull_request_label|合并请求被贴上标签| @@ -1337,11 +1337,11 @@ await octokit.request('POST /api/yystopf/ceshi/webhooks.json') |delete|分支或标签删除| |fork|仓库被fork| |push|git仓库推送| -|issue|易修已打开、已关闭、已重新打开或编辑| -|issue_assign|易修被指派| -|issue_label|易修标签被更新或删除| -|issue_milestone|易修被收入里程碑| -|issue_comment|易修评论| +|issue|疑修已打开、已关闭、已重新打开或编辑| +|issue_assign|疑修被指派| +|issue_label|疑修标签被更新或删除| +|issue_milestone|疑修被收入里程碑| +|issue_comment|疑修评论| |pull_request|合并请求| |pull_request_assign|合并请求被指派| |pull_request_label|合并请求被贴上标签| @@ -1440,11 +1440,11 @@ await octokit.request('PATCH /api/yystopf/ceshi/webhooks/7.json') |delete|分支或标签删除| |fork|仓库被fork| |push|git仓库推送| -|issue|易修已打开、已关闭、已重新打开或编辑| -|issue_assign|易修被指派| -|issue_label|易修标签被更新或删除| -|issue_milestone|易修被收入里程碑| -|issue_comment|易修评论| +|issue|疑修已打开、已关闭、已重新打开或编辑| +|issue_assign|疑修被指派| +|issue_label|疑修标签被更新或删除| +|issue_milestone|疑修被收入里程碑| +|issue_comment|疑修评论| |pull_request|合并请求| |pull_request_assign|合并请求被指派| |pull_request_label|合并请求被贴上标签| diff --git a/app/docs/slate/source/includes/_users.md b/app/docs/slate/source/includes/_users.md index 218a1a4b..f416d488 100644 --- a/app/docs/slate/source/includes/_users.md +++ b/app/docs/slate/source/includes/_users.md @@ -90,13 +90,13 @@ await octokit.request('GET /api/users/:login/messages.json') #### 消息来源source字段说明 类型|说明 --------- | ----------- -|IssueAssigned | 有新指派给我的易修 | -|IssueAssignerExpire | 我负责的易修截止日期到达最后一天 | -|IssueAtme | 在易修中@我 | -|IssueChanged | 我创建或负责的易修状态变更 | -|IssueCreatorExpire | 我创建的易修截止日期到达最后一天 | -|IssueDeleted | 我创建或负责的易修删除 | -|IssueJournal | 我创建或负责的易修有新的评论 | +|IssueAssigned | 有新指派给我的疑修 | +|IssueAssignerExpire | 我负责的疑修截止日期到达最后一天 | +|IssueAtme | 在疑修中@我 | +|IssueChanged | 我创建或负责的疑修状态变更 | +|IssueCreatorExpire | 我创建的疑修截止日期到达最后一天 | +|IssueDeleted | 我创建或负责的疑修删除 | +|IssueJournal | 我创建或负责的疑修有新的评论 | |LoginIpTip | 登录异常提示 | |OrganizationJoined | 账号被拉入组织 | |OrganizationLeft | 账号被移出组织 | @@ -104,7 +104,7 @@ await octokit.request('GET /api/users/:login/messages.json') |ProjectDeleted | 我关注的仓库被删除 | |ProjectFollowed | 我管理的仓库被关注 | |ProjectForked | 我管理的仓库被复刻 | -|ProjectIssue | 我管理/关注的仓库有新的易修 | +|ProjectIssue | 我管理/关注的仓库有新的疑修 | |ProjectJoined | 账号被拉入项目 | |ProjectLeft | 账号被移出项目 | |ProjectMemberJoined | 我管理的仓库有成员加入 | @@ -250,7 +250,7 @@ await octokit.request('POST /api/users/:login/messages.json') --------- | ----------- | ----------- |type | string | 消息类型 | |receivers_login | array | 需要发送消息的用户名数组| -|atmeable_type | string | atme消息对象,是从哪里@我的,比如评论:Journal、易修:Issue、合并请求:PullRequest | +|atmeable_type | string | atme消息对象,是从哪里@我的,比如评论:Journal、疑修:Issue、合并请求:PullRequest | |atmeable_id | integer | atme消息对象id | > 请求的JSON示例: @@ -469,7 +469,7 @@ await octokit.request('GET /api/template_message_settings.json') "total_settings_count": 4, "settings": [ { - "name": "易修被指派", + "name": "疑修被指派", "key": "IssueAssigned", "notification_disabled": true, "email_disabled": false @@ -488,7 +488,7 @@ await octokit.request('GET /api/template_message_settings.json') "total_settings_count": 4, "settings": [ { - "name": "有新的易修", + "name": "有新的疑修", "key": "Issue", "notification_disabled": true, "email_disabled": false @@ -875,7 +875,7 @@ await octokit.request('GET /api/users/:login/statistics/activity.json') 参数 | 类型 | 字段说明 --------- | ----------- | ----------- |dates |array |时间 | -|issues_count |array |易修数量| +|issues_count |array |疑修数量| |pull_requests_count |array |合并请求数量| |commtis_count |array |贡献数量| @@ -1084,7 +1084,7 @@ await octokit.request('GET /api/users/:login/project_trends.json') 参数 | 类型 | 字段说明 --------- | ----------- | ----------- |total_count |int |所选时间内的总动态数 | -|project_trends.trend_type |string|动态类型,Issue:易修,VersionRelease:版本发布,PullRequest:合并请求| +|project_trends.trend_type |string|动态类型,Issue:疑修,VersionRelease:版本发布,PullRequest:合并请求| |project_trends.action_type |string|操作类型| |project_trends.trend_id |integer|动态id| |project_trends.user_name |string|用户名称| diff --git a/app/forms/organizations/create_form.rb b/app/forms/organizations/create_form.rb index 48c8e23d..2163f477 100644 --- a/app/forms/organizations/create_form.rb +++ b/app/forms/organizations/create_form.rb @@ -1,6 +1,6 @@ class Organizations::CreateForm < BaseForm NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 - attr_accessor :name, :description, :website, :location, :repo_admin_change_team_access, :visibility, :max_repo_creation, :nickname + attr_accessor :name, :description, :website, :location, :repo_admin_change_team_access, :visibility, :max_repo_creation, :nickname, :original_name validates :name, :nickname, :visibility, presence: true validates :name, :nickname, length: { maximum: 100 } @@ -9,7 +9,7 @@ class Organizations::CreateForm < BaseForm validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } validate do - check_name(name) unless name.blank? + check_name(name) unless name.blank? || name == original_name end def check_name(name) diff --git a/app/forms/register/remote_form.rb b/app/forms/register/remote_form.rb new file mode 100644 index 00000000..59a5fbc8 --- /dev/null +++ b/app/forms/register/remote_form.rb @@ -0,0 +1,16 @@ +module Register + class RemoteForm < Register::BaseForm + # login 登陆方式,支持邮箱、登陆、手机号等 + attr_accessor :username, :email, :password, :platform + + validates :username, :email, :password, presence: true + validate :check! + + def check! + Rails.logger.info "Register::RemoteForm params: username: #{username}; email: #{email}; password: #{password}; platform: #{platform}" + check_login(username) + check_mail(email) + check_password(password) + end + end +end diff --git a/app/forms/users/login_form.rb b/app/forms/users/login_form.rb index 2634a36c..8ae917e0 100644 --- a/app/forms/users/login_form.rb +++ b/app/forms/users/login_form.rb @@ -4,5 +4,5 @@ class Users::LoginForm attr_accessor :password, :login validates :login, presence: true - validates :password, presence: true, length: { minimum: 8, maximum: 16 }, format: { with: CustomRegexp::PASSWORD, message: "8~16位,支持字母数字和符号" } + validates :password, presence: true, length: { minimum: 6, maximum: 16 }, format: { with: CustomRegexp::LOGIN_PASSWORD, message: "6~16位,支持字母数字和符号" } end \ No newline at end of file diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 21ab6fb4..b357d40b 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -15,7 +15,7 @@ module RepositoriesHelper end def image_type?(str) - default_type = %w(png jpg gif tif psd svg bmp webp jpeg) + default_type = %w(png jpg gif tif psd svg bmp webp jpeg ico psd) default_type.include?(str&.downcase) end @@ -45,7 +45,7 @@ module RepositoriesHelper end end - def readme_render_decode64_content(str, path) + def readme_render_decode64_content(str, owner, repo, ref) return nil if str.blank? begin content = Base64.decode64(str).force_encoding('UTF-8') @@ -63,13 +63,16 @@ module RepositoriesHelper 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 + # 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 = "#{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 @@ -94,7 +97,7 @@ module RepositoriesHelper 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, path) + readme_render_decode64_content(content, owner, repo, ref) else file_type = File.extname(entry['name'].to_s)[1..-1] if image_type?(file_type) diff --git a/app/helpers/tag_chosen_helper.rb b/app/helpers/tag_chosen_helper.rb index 593e412d..dcf3c457 100644 --- a/app/helpers/tag_chosen_helper.rb +++ b/app/helpers/tag_chosen_helper.rb @@ -26,8 +26,13 @@ module TagChosenHelper end def render_branches(project) - branches = Gitea::Repository::Branches::ListService.call(project&.owner, project.identifier) - branches.collect{|i| i["name"] if i.is_a?(Hash)} + if project.educoder? + return ['master'] + else + branches = Gitea::Repository::Branches::ListNameService.call(project&.owner, project.identifier) + return branches.collect{|i| i["name"] if i.is_a?(Hash)} if branches.is_a?(Array) + return branches["branch_name"] if branches.is_a?(Hash) + end end def render_cache_trackers diff --git a/app/jobs/migrate_remote_repository_job.rb b/app/jobs/migrate_remote_repository_job.rb index e54eaf2b..48814191 100644 --- a/app/jobs/migrate_remote_repository_job.rb +++ b/app/jobs/migrate_remote_repository_job.rb @@ -6,7 +6,7 @@ class MigrateRemoteRepositoryJob < ApplicationJob return if repo.blank? puts "############ MigrateRemoteRepositoryJob starting ... ############" - + params.except!(:auth_password, :auth_username) if params[:auth_username].nil? gitea_repository = Gitea::Repository::MigrateService.new(token, params).call puts "#gitea_repository#{gitea_repository}" if gitea_repository[0]==201 diff --git a/app/jobs/send_template_message_job.rb b/app/jobs/send_template_message_job.rb index 125ed3e0..f1775542 100644 --- a/app/jobs/send_template_message_job.rb +++ b/app/jobs/send_template_message_job.rb @@ -37,6 +37,7 @@ class SendTemplateMessageJob < ApplicationJob issue = Issue.find_by_id(issue_id) return unless operator.present? && issue.present? # receivers = receivers.where.not(id: operator&.id) + receivers = User.where(id: receivers) receivers_string, content, notification_url = MessageTemplate::IssueAtme.get_message_content(receivers, operator, issue) Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {operator_id: operator.id, issue_id: issue.id}, 2, operator_id) when 'IssueChanged' @@ -93,18 +94,6 @@ class SendTemplateMessageJob < ApplicationJob receivers_email_string, email_title, email_content = MessageTemplate::OrganizationLeft.get_email_message_content(receiver, organization) Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) end - when 'OrganizationRole' - user_id, organization_id, role = args[0], args[1], args[2] - user = User.find_by_id(user_id) - organization = Organization.find_by_id(organization_id) - return unless user.present? && organization.present? - receivers = User.where(id: user.id) - receivers_string, content, notification_url = MessageTemplate::OrganizationRole.get_message_content(receivers, organization, role) - Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {user_id: user.id, organization_id: organization.id, role: role}) - receivers.find_each do |receiver| - receivers_email_string, email_title, email_content = MessageTemplate::OrganizationRole.get_email_message_content(receiver, organization, role) - Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) - end when 'ProjectIssue' operator_id, issue_id = args[0], args[1] operator = User.find_by_id(operator_id) @@ -235,6 +224,7 @@ class SendTemplateMessageJob < ApplicationJob pull_request = PullRequest.find_by_id(pull_request_id) return unless operator.present? && pull_request.present? # receivers = receivers.where.not(id: operator&.id) + receivers = User.where(id: receivers) receivers_string, content, notification_url = MessageTemplate::PullRequestAtme.get_message_content(receivers, operator, pull_request) Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {operator_id: operator.id, pull_request_id: pull_request.id}, 2, operator_id) when 'PullRequestChanged' @@ -274,6 +264,32 @@ class SendTemplateMessageJob < ApplicationJob receivers_email_string, email_title, email_content = MessageTemplate::PullRequestMerged.get_email_message_content(receiver, operator, pull_request) Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) end + when 'TeamJoined' + user_id, organization_id, team_id = args[0], args[1], args[2] + user = User.find_by_id(user_id) + organization = Organization.find_by_id(organization_id) + team = Team.find_by_id(team_id) + return unless user.present? && organization.present? && team.present? + receivers = User.where(id: user.id) + receivers_string, content, notification_url = MessageTemplate::TeamJoined.get_message_content(receivers, organization, team) + Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {user_id: user.id, organization_id: organization.id, team_id: team.id}) + receivers.find_each do |receiver| + receivers_email_string, email_title, email_content = MessageTemplate::TeamJoined.get_email_message_content(receiver, organization, team) + Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) + end + when 'TeamLeft' + user_id, organization_id, team_id = args[0], args[1], args[2] + user = User.find_by_id(user_id) + organization = Organization.find_by_id(organization_id) + team = Team.find_by_id(team_id) + return unless user.present? && organization.present? && team.present? + receivers = User.where(id: user.id) + receivers_string, content, notification_url = MessageTemplate::TeamLeft.get_message_content(receivers, organization, team) + Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {user_id: user.id, organization_id: organization.id, team_id: team.id}) + receivers.find_each do |receiver| + receivers_email_string, email_title, email_content = MessageTemplate::TeamLeft.get_email_message_content(receiver, organization, team) + Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) + end end end end \ No newline at end of file diff --git a/app/libs/custom_regexp.rb b/app/libs/custom_regexp.rb index bbc06125..25c3ae98 100644 --- a/app/libs/custom_regexp.rb +++ b/app/libs/custom_regexp.rb @@ -5,6 +5,7 @@ module CustomRegexp LASTNAME = /\A[a-zA-Z0-9\u4e00-\u9fa5]+\z/ NICKNAME = /\A[\u4e00-\u9fa5_a-zA-Z0-9]+\z/ PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{8,16}\z/ + LOGIN_PASSWORD = /\A[a-z_A-Z0-9\-\.!@#\$%\\\^&\*\)\(\+=\{\}\[\]\/",'_<>~\·`\?:;|]{6,16}\z/ URL = /\Ahttps?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+[-A-Za-z0-9+&@#\/%=~_|]\z/ IP = /^((\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/ diff --git a/app/libs/util.rb b/app/libs/util.rb index 1db9460f..691ed7ca 100644 --- a/app/libs/util.rb +++ b/app/libs/util.rb @@ -29,6 +29,7 @@ module Util file.write(io) end end + true end def download_file(url, save_path) diff --git a/app/models/claim.rb b/app/models/claim.rb new file mode 100644 index 00000000..963b5f55 --- /dev/null +++ b/app/models/claim.rb @@ -0,0 +1,21 @@ +# == Schema Information +# +# Table name: claims +# +# id :integer not null, primary key +# issue_id :integer +# user_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# note :text(65535) +# +# Indexes +# +# index_claims_on_issue_id (issue_id) +# index_claims_on_user_id (user_id) +# + +class Claim < ApplicationRecord + belongs_to :user, foreign_key: :user_id + scope :claim_includes, ->{includes(:user)} +end diff --git a/app/models/concerns/project_operable.rb b/app/models/concerns/project_operable.rb index a6dc0dd3..ee361d2c 100644 --- a/app/models/concerns/project_operable.rb +++ b/app/models/concerns/project_operable.rb @@ -21,13 +21,37 @@ module ProjectOperable end def add_member!(user_id, role_name='Developer') - member = members.create!(user_id: user_id) + if self.owner.is_a?(Organization) + case role_name + when 'Manager' + team = self.owner.teams.admin.take + team = team.nil? ? Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false) : team + TeamProject.build(self.user_id, team.id, self.id) + OrganizationUser.build(self.user_id, user_id) + team_user = TeamUser.build(self.user_id, user_id, team.id) + when 'Developer' + team = self.owner.teams.write.take + team = team.nil? ? Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false) : team + TeamProject.build(self.user_id, team.id, self.id) + OrganizationUser.build(self.user_id, user_id) + team_user = TeamUser.build(self.user_id, user_id, team.id) + when 'Reporter' + team = self.owner.teams.read.take + team = team.nil? ? Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false) : team + TeamProject.build(self.user_id, team.id, self.id) + OrganizationUser.build(self.user_id, user_id) + team_user = TeamUser.build(self.user_id, user_id, team.id) + end + end + member = members.create!(user_id: user_id, team_user_id: team_user&.id) set_developer_role(member, role_name) end def remove_member!(user_id) member = members.find_by(user_id: user_id) member.destroy! if member && self.user_id != user_id + team_user = TeamUser.find_by_id(member&.team_user_id) + team_user.destroy! if team_user end def member?(user_id) @@ -47,6 +71,28 @@ module ProjectOperable def change_member_role!(user_id, role) member = self.member(user_id) + if self.owner.is_a?(Organization) && member.team_user.present? + case role&.name + when 'Manager' + team = self.owner.teams.admin.take + team = team.nil? ? Team.build(self.user_id, 'admin', '管理员', '', 'admin', false, false) : team + TeamProject.build(self.user_id, team.id, self.id) + OrganizationUser.build(self.user_id, user_id) + team_user = member.team_user.update(team_id: team&.id) + when 'Developer' + team = self.owner.teams.write.take + team = team.nil? ? Team.build(self.user_id, 'developer', '开发者', '', 'write', false, false) : team + TeamProject.build(self.user_id, team.id, self.id) + OrganizationUser.build(self.user_id, user_id) + team_user = member.team_user.update(team_id: team&.id) + when 'Reporter' + team = self.owner.teams.read.take + team = team.nil? ? Team.build(self.user_id, 'reporter', '报告者', '', 'read', false, false) : team + TeamProject.build(self.user_id, team.id, self.id) + OrganizationUser.build(self.user_id, user_id) + team_user = member.team_user.update(team_id: team&.id) + end + end member.member_roles.last.update_attributes!(role: role) end diff --git a/app/models/gitea/webhook_task.rb b/app/models/gitea/webhook_task.rb index d19a163a..325352c6 100644 --- a/app/models/gitea/webhook_task.rb +++ b/app/models/gitea/webhook_task.rb @@ -1,7 +1,6 @@ class Gitea::WebhookTask < Gitea::Base serialize :payload_content, JSON serialize :request_content, JSON - serialize :response_content, JSON self.inheritance_column = nil @@ -10,4 +9,10 @@ class Gitea::WebhookTask < Gitea::Base belongs_to :webhook, class_name: "Gitea::Webhook", foreign_key: :hook_id enum type: {gogs: 1, slack: 2, gitea: 3, discord: 4, dingtalk: 5, telegram: 6, msteams: 7, feishu: 8, matrix: 9} + + def response_content_json + JSON.parse(response_content) + rescue + {} + end end \ No newline at end of file diff --git a/app/models/issue.rb b/app/models/issue.rb index 793cd460..47445245 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -64,6 +64,7 @@ class Issue < ApplicationRecord # has_many :memos has_many :journals, :as => :journalized, :dependent => :destroy has_many :journal_details, through: :journals + has_many :claims, :dependent => :destroy has_many :issue_tags_relates, dependent: :destroy has_many :issue_tags, through: :issue_tags_relates has_many :issue_times, dependent: :destroy @@ -154,7 +155,7 @@ class Issue < ApplicationRecord end def is_collaborators? - self.assigned_to_id.present? ? self.project.members.where(user_id: self.assigned_to_id).present? : false + self.assigned_to_id.present? ? self.project.member?(self.assigned_to_id) : false end def get_issue_tags_name diff --git a/app/models/member.rb b/app/models/member.rb index e72ae7c6..521f939c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -11,23 +11,26 @@ # course_group_id :integer default("0") # is_collect :integer default("1") # graduation_group_id :integer default("0") +# team_user_id :integer # # Indexes # # index_members_on_course_id (course_id) # index_members_on_project_id (project_id) +# index_members_on_team_user_id (team_user_id) # index_members_on_user_id (user_id) # index_members_on_user_id_and_project_id (user_id,project_id,course_id) UNIQUE # -class Member < ApplicationRecord - belongs_to :user - # belongs_to :course, optional: true - belongs_to :project, optional: true - - has_many :member_roles, dependent: :destroy - has_many :roles, through: :member_roles - - validates :user_id, :project_id, presence: true - -end +class Member < ApplicationRecord + belongs_to :user + # belongs_to :course, optional: true + belongs_to :project, optional: true + belongs_to :team_user, optional: true + + has_many :member_roles, dependent: :destroy + has_many :roles, through: :member_roles + + validates :user_id, :project_id, presence: true + +end diff --git a/app/models/message_template.rb b/app/models/message_template.rb index a6e894c7..c9812e51 100644 --- a/app/models/message_template.rb +++ b/app/models/message_template.rb @@ -17,27 +17,25 @@ class MessageTemplate < ApplicationRecord def self.build_init_data self.create(type: 'MessageTemplate::FollowedTip', sys_notice: '{nickname} 关注了你', notification_url: '{baseurl}/{login}') email_html = File.read("#{email_template_html_dir}/issue_assigned.html") - self.create(type: 'MessageTemplate::IssueAssigned', sys_notice: '{nickname1}在 {nickname2}/{repository} 指派给你一个易修:{title}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: 'GitLink: {nickname1} 在 {nickname2}/{repository} 指派给你一个易修') - self.create(type: 'MessageTemplate::IssueAssignerExpire', sys_notice: '您负责的易修 {title} 已临近截止日期,请尽快处理', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') - self.create(type: 'MessageTemplate::IssueAtme', sys_notice: '{nickname} 在易修 {title} 中@我', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') + self.create(type: 'MessageTemplate::IssueAssigned', sys_notice: '{nickname1}在 {nickname2}/{repository} 指派给你一个疑修:{title}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: 'GitLink: {nickname1} 在 {nickname2}/{repository} 指派给你一个疑修') + self.create(type: 'MessageTemplate::IssueAssignerExpire', sys_notice: '您负责的疑修 {title} 已临近截止日期,请尽快处理', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') + self.create(type: 'MessageTemplate::IssueAtme', sys_notice: '{nickname} 在疑修 {title} 中@我', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') email_html = File.read("#{email_template_html_dir}/issue_changed.html") - self.create(type: 'MessageTemplate::IssueChanged', sys_notice: '在项目 {nickname2}/{repository} 的易修 {title} 中:{ifassigner}{nickname1}将负责人从 {assigner1} 修改为 {assigner2} {endassigner}{ifstatus}{nickname1}将状态从 {status1} 修改为 {status2} {endstatus}{iftracker}{nickname1}将类型从 {tracker1} 修改为 {tracker2} {endtracker}{ifpriority}{nickname1}将优先级从 {priority1} 修改为 {priority2} {endpriority}{ifmilestone}{nickname1}将里程碑从 {milestone1} 修改为 {milestone2} {endmilestone}{iftag}{nickname1}将标记从 {tag1} 修改为 {tag2} {endtag}{ifdoneratio}{nickname1}将完成度从 {doneratio1} 修改为 {doneratio2} {enddoneratio}{ifbranch}{nickname1}将指定分支从 {branch1} 修改为 {branch2} {endbranch}{ifstartdate}{nickname1}将开始日期从 {startdate1} 修改为 {startdate2} {endstartdate}{ifduedate}{nickname1}将结束日期从 {duedate1} 修改为 {duedate2} {endduedate}', email: email_html, email_title: 'GitLink: 易修 {title} 有状态变更', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') - self.create(type: 'MessageTemplate::IssueCreatorExpire', sys_notice: '您发布的易修 {title} 已临近截止日期,请尽快处理', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') + self.create(type: 'MessageTemplate::IssueChanged', sys_notice: '在项目 {nickname2}/{repository} 的疑修 {title} 中:{ifassigner}{nickname1}将负责人从 {assigner1} 修改为 {assigner2} {endassigner}{ifstatus}{nickname1}将状态从 {status1} 修改为 {status2} {endstatus}{iftracker}{nickname1}将类型从 {tracker1} 修改为 {tracker2} {endtracker}{ifpriority}{nickname1}将优先级从 {priority1} 修改为 {priority2} {endpriority}{ifmilestone}{nickname1}将里程碑从 {milestone1} 修改为 {milestone2} {endmilestone}{iftag}{nickname1}将标记从 {tag1} 修改为 {tag2} {endtag}{ifdoneratio}{nickname1}将完成度从 {doneratio1} 修改为 {doneratio2} {enddoneratio}{ifbranch}{nickname1}将指定分支从 {branch1} 修改为 {branch2} {endbranch}{ifstartdate}{nickname1}将开始日期从 {startdate1} 修改为 {startdate2} {endstartdate}{ifduedate}{nickname1}将结束日期从 {duedate1} 修改为 {duedate2} {endduedate}', email: email_html, email_title: 'GitLink: 疑修 {title} 有状态变更', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') + self.create(type: 'MessageTemplate::IssueCreatorExpire', sys_notice: '您发布的疑修 {title} 已临近截止日期,请尽快处理', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') email_html = File.read("#{email_template_html_dir}/issue_deleted.html") - self.create(type: 'MessageTemplate::IssueDeleted', sys_notice: '{nickname}已将易修 {title} 删除', email: email_html, email_title: 'GitLink: 易修 {title} 有状态变更', notification_url: '') - self.create(type: 'MessageTemplate::IssueJournal', sys_notice: '{nickname}评论易修{title}:{notes}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') + self.create(type: 'MessageTemplate::IssueDeleted', sys_notice: '{nickname}已将疑修 {title} 删除', email: email_html, email_title: 'GitLink: 疑修 {title} 有状态变更', notification_url: '') + self.create(type: 'MessageTemplate::IssueJournal', sys_notice: '{nickname}评论疑修{title}:{notes}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}') self.create(type: 'MessageTemplate::LoginIpTip', sys_notice: '您的账号{nickname}于{login_time)在非常用的IP地址{ip}登录,如非本人操作,请立即修改密码', notification_url: '') email_html = File.read("#{email_template_html_dir}/organization_joined.html") self.create(type: 'MessageTemplate::OrganizationJoined', sys_notice: '你已加入 {organization} 组织', notification_url: '{baseurl}/{login}', email: email_html, email_title: 'GitLink: 你已加入 {organization} 组织') email_html = File.read("#{email_template_html_dir}/organization_left.html") self.create(type: 'MessageTemplate::OrganizationLeft', sys_notice: '你已被移出 {organization} 组织', notification_url: '', email: email_html, email_title: 'GitLink: 你已被移出 {organization} 组织') - email_html = File.read("#{email_template_html_dir}/organization_role.html") - self.create(type: 'MessageTemplate::OrganizationRole', sys_notice: '组织 {organization} 已把你的角色改为 {role}', email: email_html, email_title: 'GitLink: 在 {organization} 组织你的账号有权限变更', notification_url: '{baseurl}/{login}') self.create(type: 'MessageTemplate::ProjectDeleted', sys_notice: '你关注的仓库{nickname}/{repository}已被删除', notification_url: '') self.create(type: 'MessageTemplate::ProjectFollowed', sys_notice: '{nickname} 关注了你管理的仓库', notification_url: '{baseurl}/{login}') self.create(type: 'MessageTemplate::ProjectForked', sys_notice: '{nickname1} 复刻了你管理的仓库{nickname1}/{repository1}到{nickname2}/{repository2}', notification_url: '{baseurl}/{owner}/{identifier}') email_html = File.read("#{email_template_html_dir}/project_issue.html") - self.create(type: 'MessageTemplate::ProjectIssue', sys_notice: '{nickname1}在 {nickname2}/{repository} 新建易修:{title}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: 'GitLink: {nickname1} 在 {nickname2}/{repository} 新建了一个易修') + self.create(type: 'MessageTemplate::ProjectIssue', sys_notice: '{nickname1}在 {nickname2}/{repository} 新建疑修:{title}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: 'GitLink: {nickname1} 在 {nickname2}/{repository} 新建了一个疑修') email_html = File.read("#{email_template_html_dir}/project_joined.html") self.create(type: 'MessageTemplate::ProjectJoined', sys_notice: '你已加入 {repository} 项目', notification_url: '{baseurl}/{owner}/{identifier}', email: email_html, email_title: 'GitLink: 你已加入 {repository} 项目') email_html = File.read("#{email_template_html_dir}/project_left.html") @@ -66,6 +64,10 @@ class MessageTemplate < ApplicationRecord self.create(type: 'MessageTemplate::PullRequestJournal', sys_notice: '{nickname}评论合并请求{title}:{notes}', notification_url: '{baseurl}/{owner}/{identifier}/pulls/{id}') email_html = File.read("#{email_template_html_dir}/pull_request_merged.html") self.create(type: 'MessageTemplate::PullRequestMerged', sys_notice: '你提交的合并请求:{title} 已通过', email: email_html, email_title: 'GitLink: 合并请求 {title} 有状态变更', notification_url: '{baseurl}/{owner}/{identifier}/pulls/{id}') + email_html = File.read("#{email_template_html_dir}/team_joined.html") + self.create(type: 'MessageTemplate::TeamJoined', sys_notice: '你已被拉入组织 {organization} 的 {team} 团队,拥有{role}权限', email: email_html, email_title: 'GitLink: 在 {organization} 组织你的账号有权限变更', notification_url: '{baseurl}/{login}') + email_html = File.read("#{email_template_html_dir}/team_left.html") + self.create(type: 'MessageTemplate::TeamLeft', sys_notice: '你已被移出组织 {organization} 的 {team} 团队', email: email_html, email_title: 'GitLink: 在 {organization} 组织你的账号有权限变更', notification_url: '{baseurl}/{login}') end def self.sys_notice diff --git a/app/models/message_template/issue_assigned.rb b/app/models/message_template/issue_assigned.rb index 10ad5eb2..7ce7f10c 100644 --- a/app/models/message_template/issue_assigned.rb +++ b/app/models/message_template/issue_assigned.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# 有新指派给我的易修 +# 有新指派给我的疑修 class MessageTemplate::IssueAssigned < MessageTemplate # MessageTemplate::IssueAssigned.get_message_content(User.where(login: 'yystopf'), User.last, Issue.last) diff --git a/app/models/message_template/issue_assigner_expire.rb b/app/models/message_template/issue_assigner_expire.rb index 405d44bb..610c5117 100644 --- a/app/models/message_template/issue_assigner_expire.rb +++ b/app/models/message_template/issue_assigner_expire.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# 我负责的易修截止日期到达最后一天 +# 我负责的疑修截止日期到达最后一天 class MessageTemplate::IssueAssignerExpire < MessageTemplate # MessageTemplate::IssueAssignerExpire.get_message_content(User.where(login: 'yystopf'), Issue.last) diff --git a/app/models/message_template/issue_atme.rb b/app/models/message_template/issue_atme.rb index d9dfd195..04eb132e 100644 --- a/app/models/message_template/issue_atme.rb +++ b/app/models/message_template/issue_atme.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# 在易修中@我 +# 在疑修中@我 class MessageTemplate::IssueAtme < MessageTemplate # MessageTemplate::IssueAtme.get_message_content(User.where(login: 'yystopf'), User.last, Issue.last) diff --git a/app/models/message_template/issue_changed.rb b/app/models/message_template/issue_changed.rb index 10007cfb..4d9e132c 100644 --- a/app/models/message_template/issue_changed.rb +++ b/app/models/message_template/issue_changed.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# 我创建或负责的易修状态变更 +# 我创建或负责的疑修状态变更 class MessageTemplate::IssueChanged < MessageTemplate # MessageTemplate::IssueChanged.get_message_content(User.where(login: 'yystopf'), User.last, Issue.last, {status_id: [1, 2], assigned_to_id: [nil, 203], tracker_id: [4, 3], priority_id: [2, 4], fixed_version_id: [nil, 5], due_date: ['', '2021-09-11'], done_ratio: [0, 40], issue_tags_value: ["", "7"], branch_name: ["", "master"]}) @@ -29,7 +29,7 @@ class MessageTemplate::IssueChanged < MessageTemplate content = MessageTemplate::IssueChanged.sys_notice.gsub('{nickname1}', operator&.real_name).gsub('{nickname2}', owner&.real_name).gsub('{repository}', project&.name).gsub('{title}', issue&.subject) url = notification_url.gsub('{owner}', owner&.login).gsub('{identifier}', project&.identifier).gsub('{id}', issue&.id.to_s) change_count = change_params.keys.size - # 易修负责人修改 + # 疑修负责人修改 if change_params[:assigned_to_id].present? assigner1 = User.find_by_id(change_params[:assigned_to_id][0]) assigner2 = User.find_by_id(change_params[:assigned_to_id][1]) @@ -44,7 +44,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifassigner})(.*)({endassigner})/, '') end - # 易修状态修改 + # 疑修状态修改 if change_params[:status_id].present? status1 = IssueStatus.find_by_id(change_params[:status_id][0]) status2 = IssueStatus.find_by_id(change_params[:status_id][1]) @@ -59,7 +59,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifstatus})(.*)({endstatus})/, '') end - # 易修类型修改 + # 疑修类型修改 if change_params[:tracker_id].present? tracker1 = Tracker.find_by_id(change_params[:tracker_id][0]) tracker2 = Tracker.find_by_id(change_params[:tracker_id][1]) @@ -74,7 +74,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({iftracker})(.*)({endtracker})/, '') end - # 易修里程碑修改 + # 疑修里程碑修改 if change_params[:fixed_version_id].present? fix_version1 = Version.find_by_id(change_params[:fixed_version_id][0]) fix_version2 = Version.find_by_id(change_params[:fixed_version_id][1]) @@ -89,7 +89,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifmilestone})(.*)({endmilestone})/, '') end - # 易修标记修改 + # 疑修标记修改 if change_params[:issue_tags_value].present? issue_tags1 = IssueTag.where(id: change_params[:issue_tags_value][0]).distinct issue_tags2 = IssueTag.where(id: change_params[:issue_tags_value][1]).distinct @@ -106,7 +106,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({iftag})(.*)({endtag})()/, '') end - # 易修优先级修改 + # 疑修优先级修改 if change_params[:priority_id].present? priority1 = IssuePriority.find_by_id(change_params[:priority_id][0]) priority2 = IssuePriority.find_by_id(change_params[:priority_id][1]) @@ -121,7 +121,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifpriority})(.*)({endpriority})/, '') end - # 易修完成度修改 + # 疑修完成度修改 if change_params[:done_ratio].present? doneratio1 = change_params[:done_ratio][0] doneratio2 = change_params[:done_ratio][1] @@ -136,7 +136,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifdoneratio})(.*)({enddoneratio})/, '') end - # 易修指定分支修改 + # 疑修指定分支修改 if change_params[:branch_name].present? branch1 = change_params[:branch_name][0].blank? ? '分支未指定' : change_params[:branch_name][0] branch2 = change_params[:branch_name][1].blank? ? '分支未指定' : change_params[:branch_name][1] @@ -151,7 +151,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifbranch})(.*)({endbranch})/, '') end - # 易修开始日期修改 + # 疑修开始日期修改 if change_params[:start_date].present? startdate1 = change_params[:start_date][0].blank? ? "未选择开始日期" : change_params[:start_date][0] startdate2 = change_params[:start_date][1].blank? ? "未选择开始日期" : change_params[:start_date][1] @@ -166,7 +166,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifstartdate})(.*)({endstartdate})/, '') end - # 易修结束日期修改 + # 疑修结束日期修改 if change_params[:due_date].present? duedate1 = change_params[:due_date][0].blank? ? '未选择结束日期' : change_params[:due_date][0] duedate2 = change_params[:due_date][1].blank? ? '未选择结束日期' : change_params[:due_date][1] @@ -207,7 +207,7 @@ class MessageTemplate::IssueChanged < MessageTemplate content.gsub!('{title}', issue&.subject) content.gsub!('{id}', issue&.id.to_s) change_count = change_params.keys.size - # 易修负责人修改 + # 疑修负责人修改 if change_params[:assigned_to_id].present? assigner1 = User.find_by_id(change_params[:assigned_to_id][0]) assigner2 = User.find_by_id(change_params[:assigned_to_id][1]) @@ -222,7 +222,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifassigner})(.*)({endassigner})/, '') end - # 易修状态修改 + # 疑修状态修改 if change_params[:status_id].present? status1 = IssueStatus.find_by_id(change_params[:status_id][0]) status2 = IssueStatus.find_by_id(change_params[:status_id][1]) @@ -237,7 +237,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifstatus})(.*)({endstatus})/, '') end - # 易修类型修改 + # 疑修类型修改 if change_params[:tracker_id].present? tracker1 = Tracker.find_by_id(change_params[:tracker_id][0]) tracker2 = Tracker.find_by_id(change_params[:tracker_id][1]) @@ -252,7 +252,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({iftracker})(.*)({endtracker})/, '') end - # 易修里程碑修改 + # 疑修里程碑修改 if change_params[:fixed_version_id].present? fix_version1 = Version.find_by_id(change_params[:fixed_version_id][0]) fix_version2 = Version.find_by_id(change_params[:fixed_version_id][1]) @@ -267,7 +267,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifmilestone})(.*)({endmilestone})/, '') end - # 易修标记修改 + # 疑修标记修改 if change_params[:issue_tags_value].present? issue_tags1 = IssueTag.where(id: change_params[:issue_tags_value][0]).distinct issue_tags2 = IssueTag.where(id: change_params[:issue_tags_value][1]).distinct @@ -284,7 +284,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({iftag})(.*)({endtag})()/, '') end - # 易修优先级修改 + # 疑修优先级修改 if change_params[:priority_id].present? priority1 = IssuePriority.find_by_id(change_params[:priority_id][0]) priority2 = IssuePriority.find_by_id(change_params[:priority_id][1]) @@ -299,7 +299,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifpriority})(.*)({endpriority})/, '') end - # 易修完成度修改 + # 疑修完成度修改 if change_params[:done_ratio].present? doneratio1 = change_params[:done_ratio][0] doneratio2 = change_params[:done_ratio][1] @@ -314,7 +314,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifdoneratio})(.*)({enddoneratio})/, '') end - # 易修指定分支修改 + # 疑修指定分支修改 if change_params[:branch_name].present? branch1 = change_params[:branch_name][0].blank? ? '分支未指定' : change_params[:branch_name][0] branch2 = change_params[:branch_name][1].blank? ? '分支未指定' : change_params[:branch_name][1] @@ -329,7 +329,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifbranch})(.*)({endbranch})/, '') end - # 易修开始日期修改 + # 疑修开始日期修改 if change_params[:start_date].present? startdate1 = change_params[:start_date][0].blank? ? "未选择开始日期" : change_params[:start_date][0] startdate2 = change_params[:start_date][1].blank? ? "未选择开始日期" : change_params[:start_date][1] @@ -344,7 +344,7 @@ class MessageTemplate::IssueChanged < MessageTemplate else content.gsub!(/({ifstartdate})(.*)({endstartdate})/, '') end - # 易修结束日期修改 + # 疑修结束日期修改 if change_params[:due_date].present? duedate1 = change_params[:due_date][0].blank? ? '未选择结束日期' : change_params[:due_date][0] duedate2 = change_params[:due_date][1].blank? ? '未选择结束日期' : change_params[:due_date][1] diff --git a/app/models/message_template/issue_creator_expire.rb b/app/models/message_template/issue_creator_expire.rb index 38ef2fe2..475c05ce 100644 --- a/app/models/message_template/issue_creator_expire.rb +++ b/app/models/message_template/issue_creator_expire.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# 我创建的易修截止日期到达最后一天 +# 我创建的疑修截止日期到达最后一天 class MessageTemplate::IssueCreatorExpire < MessageTemplate # MessageTemplate::IssueCreatorExpire.get_message_content(User.where(login: 'yystopf'), Issue.last) diff --git a/app/models/message_template/issue_deleted.rb b/app/models/message_template/issue_deleted.rb index d51782cf..a3eae75d 100644 --- a/app/models/message_template/issue_deleted.rb +++ b/app/models/message_template/issue_deleted.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# 我创建或负责的易修删除 +# 我创建或负责的疑修删除 class MessageTemplate::IssueDeleted < MessageTemplate # MessageTemplate::IssueDeleted.get_message_content(User.where(login: 'yystopf'), User.last, "hahah") diff --git a/app/models/message_template/issue_journal.rb b/app/models/message_template/issue_journal.rb index 0b385147..3286776a 100644 --- a/app/models/message_template/issue_journal.rb +++ b/app/models/message_template/issue_journal.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# TODO 我创建或负责的易修有新的评论 +# TODO 我创建或负责的疑修有新的评论 class MessageTemplate::IssueJournal < MessageTemplate # MessageTemplate::IssueJournal.get_message_content(User.where(login: 'yystopf')) diff --git a/app/models/message_template/project_issue.rb b/app/models/message_template/project_issue.rb index 8d7d99aa..5904a428 100644 --- a/app/models/message_template/project_issue.rb +++ b/app/models/message_template/project_issue.rb @@ -12,7 +12,7 @@ # email_title :string(255) # -# TODO 我管理/关注的仓库有新的易修 +# TODO 我管理/关注的仓库有新的疑修 class MessageTemplate::ProjectIssue < MessageTemplate # MessageTemplate::ProjectIssue.get_message_content(User.where(login: 'yystopf'), User.where(login: 'forgetest1'), User.last, Issue.last) diff --git a/app/models/message_template/project_setting_changed.rb b/app/models/message_template/project_setting_changed.rb index 3ce17433..49cf7821 100644 --- a/app/models/message_template/project_setting_changed.rb +++ b/app/models/message_template/project_setting_changed.rb @@ -15,7 +15,7 @@ # 我管理的仓库项目设置被更改 class MessageTemplate::ProjectSettingChanged < MessageTemplate - # MessageTemplate::ProjectSettingChanged.get_message_content(User.where(login: 'yystopf'), User.last, Project.last, {description: '测试修改项目简介', category: '大数据', language: 'Ruby', permission: '公有', navbar: '易修, 合并请求'}) + # MessageTemplate::ProjectSettingChanged.get_message_content(User.where(login: 'yystopf'), User.last, Project.last, {description: '测试修改项目简介', category: '大数据', language: 'Ruby', permission: '公有', navbar: '疑修, 合并请求'}) def self.get_message_content(receivers, operator, project, change_params) receivers.each do |receiver| if receiver.user_template_message_setting.present? @@ -135,7 +135,7 @@ class MessageTemplate::ProjectSettingChanged < MessageTemplate unit_types.unshift('主页') unit_types.append('动态') navbar = unit_types.join(',') - navbar.gsub!('issues', '易修') + navbar.gsub!('issues', '疑修') navbar.gsub!('pulls', '合并请求') navbar.gsub!('wiki', 'Wiki') navbar.gsub!('devops', '工作流') @@ -284,7 +284,7 @@ class MessageTemplate::ProjectSettingChanged < MessageTemplate unit_types.unshift('主页') unit_types.append('动态') navbar = unit_types.join(',') - navbar.gsub!('issues', '易修') + navbar.gsub!('issues', '疑修') navbar.gsub!('pulls', '合并请求') navbar.gsub!('wiki', 'Wiki') navbar.gsub!('devops', '工作流') diff --git a/app/models/message_template/team_joined.rb b/app/models/message_template/team_joined.rb new file mode 100644 index 00000000..f4994095 --- /dev/null +++ b/app/models/message_template/team_joined.rb @@ -0,0 +1,58 @@ +# == Schema Information +# +# Table name: message_templates +# +# id :integer not null, primary key +# type :string(255) +# sys_notice :text(65535) +# email :text(65535) +# created_at :datetime not null +# updated_at :datetime not null +# notification_url :string(255) +# email_title :string(255) +# + +# 账号被拉入组织团队 +class MessageTemplate::TeamJoined < MessageTemplate + + # MessageTemplate::TeamJoined.get_message_content(User.where(login: 'yystopf'), Organization.last, Organization.last.teams.take) + def self.get_message_content(receivers, organization, team) + receivers.each do |receiver| + if receiver.user_template_message_setting.present? + receivers = receivers.where.not(id: receiver.id) unless receiver.user_template_message_setting.notification_body["Normal::Permission"] + end + end + return '', '', '' if receivers.blank? + content = sys_notice.gsub('{organization}', organization&.real_name).gsub('{team}', team&.nickname).gsub('{role}', team&.authorize_name) + url = notification_url.gsub('{login}', organization&.login) + return receivers_string(receivers), content, url + rescue => e + Rails.logger.info("MessageTemplate::TeamJoined.get_message_content [ERROR] #{e}") + return '', '', '' + end + + def self.get_email_message_content(receiver, organization, role) + if receiver.user_template_message_setting.present? + return '', '', '' unless receiver.user_template_message_setting.email_body["Normal::Permission"] + title = email_title + title.gsub!('{organization}', organization&.real_name) + title.gsub!('{team}', team&.nickname) + title.gsub!('{role}', team&.authorize_name) + content = email + content.gsub!('{receiver}', receiver&.real_name) + content.gsub!('{baseurl}', base_url) + content.gsub!('{login}', organization&.login) + content.gsub!('{organization}', organization&.real_name) + content.gsub!('{team}', team&.nickname) + content.gsub!('{role}', team&.authorize_name) + + return receiver&.mail, title, content + else + return '', '', '' + end + + rescue => e + Rails.logger.info("MessageTemplate::TeamJoined.get_email_message_content [ERROR] #{e}") + return '', '', '' + end +end diff --git a/app/models/message_template/team_left.rb b/app/models/message_template/team_left.rb new file mode 100644 index 00000000..57242263 --- /dev/null +++ b/app/models/message_template/team_left.rb @@ -0,0 +1,58 @@ +# == Schema Information +# +# Table name: message_templates +# +# id :integer not null, primary key +# type :string(255) +# sys_notice :text(65535) +# email :text(65535) +# created_at :datetime not null +# updated_at :datetime not null +# notification_url :string(255) +# email_title :string(255) +# + +# 账号被移出组织团队 +class MessageTemplate::TeamLeft < MessageTemplate + + # MessageTemplate::TeamLeft.get_message_content(User.where(login: 'yystopf'), Organization.last, Organization.last.teams.take) + def self.get_message_content(receivers, organization, team) + receivers.each do |receiver| + if receiver.user_template_message_setting.present? + receivers = receivers.where.not(id: receiver.id) unless receiver.user_template_message_setting.notification_body["Normal::Permission"] + end + end + return '', '', '' if receivers.blank? + content = sys_notice.gsub('{organization}', organization&.real_name).gsub('{team}', team&.nickname).gsub('{role}', team&.authorize_name) + url = notification_url.gsub('{login}', organization&.login) + return receivers_string(receivers), content, url + rescue => e + Rails.logger.info("MessageTemplate::TeamLeft.get_message_content [ERROR] #{e}") + return '', '', '' + end + + def self.get_email_message_content(receiver, organization, team) + if receiver.user_template_message_setting.present? + return '', '', '' unless receiver.user_template_message_setting.email_body["Normal::Permission"] + title = email_title + title.gsub!('{organization}', organization&.real_name) + title.gsub!('{team}', team&.nickname) + title.gsub!('{role}', team&.authorize_name) + content = email + content.gsub!('{receiver}', receiver&.real_name) + content.gsub!('{baseurl}', base_url) + content.gsub!('{login}', organization&.login) + content.gsub!('{organization}', organization&.real_name) + content.gsub!('{team}', team&.nickname) + content.gsub!('{role}', team&.authorize_name) + + return receiver&.mail, title, content + else + return '', '', '' + end + + rescue => e + Rails.logger.info("MessageTemplate::TeamLeft.get_email_message_content [ERROR] #{e}") + return '', '', '' + end +end diff --git a/app/models/team.rb b/app/models/team.rb index 19d05c77..c2e63338 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -56,10 +56,10 @@ class Team < ApplicationRecord def authorize_name case self.authorize - when 'read' then '报告者' - when 'write' then '开发者' + when 'read' then '读取' + when 'write' then '写入' when 'admin' then '管理员' - when 'owner' then '拥有者' + when 'owner' then '管理员' else '' end diff --git a/app/models/team_user.rb b/app/models/team_user.rb index 9670013a..9f90bc15 100644 --- a/app/models/team_user.rb +++ b/app/models/team_user.rb @@ -22,9 +22,17 @@ class TeamUser < ApplicationRecord belongs_to :team, counter_cache: :num_users belongs_to :user + has_one :member + validates :user_id, uniqueness: {scope: [:organization_id, :team_id]} + before_destroy :remove_project_member + def self.build(organization_id, user_id, team_id) self.create!(organization_id: organization_id, user_id: user_id, team_id: team_id) end + + def remove_project_member + member.destroy if member.present? + end end diff --git a/app/models/template_message_setting/create_or_assign.rb b/app/models/template_message_setting/create_or_assign.rb index 2c9fa207..50ef8643 100644 --- a/app/models/template_message_setting/create_or_assign.rb +++ b/app/models/template_message_setting/create_or_assign.rb @@ -25,7 +25,7 @@ class TemplateMessageSetting::CreateOrAssign < TemplateMessageSetting end def self.build_init_data - self.find_or_create_by(name: "易修状态变更", key: "IssueChanged") + self.find_or_create_by(name: "疑修状态变更", key: "IssueChanged") self.find_or_create_by(name: "合并请求状态变更", key: "PullRequestChanged") end end diff --git a/app/models/template_message_setting/manage_project.rb b/app/models/template_message_setting/manage_project.rb index 2aa9e488..7a80c999 100644 --- a/app/models/template_message_setting/manage_project.rb +++ b/app/models/template_message_setting/manage_project.rb @@ -25,7 +25,7 @@ class TemplateMessageSetting::ManageProject < TemplateMessageSetting end def self.build_init_data - self.find_or_create_by(name: "有新的易修", key: "Issue") + self.find_or_create_by(name: "有新的疑修", key: "Issue") self.find_or_create_by(name: "有新的合并请求", key: "PullRequest") self.find_or_create_by(name: "有成员变动", key: "Member") self.find_or_create_by(name: "仓库设置被更改", key: "SettingChanged") diff --git a/app/models/template_message_setting/normal.rb b/app/models/template_message_setting/normal.rb index 9090196d..771fba2f 100644 --- a/app/models/template_message_setting/normal.rb +++ b/app/models/template_message_setting/normal.rb @@ -27,7 +27,7 @@ class TemplateMessageSetting::Normal < TemplateMessageSetting self.find_or_create_by(name: "账号有权限变更", key: "Permission") self.find_or_create_by(name: "被拉入或移出组织", key: "Organization") self.find_or_create_by(name: "被拉入或移出项目", key: "Project") - self.find_or_create_by(name: "有新的易修指派给我", key: "IssueAssigned") + self.find_or_create_by(name: "有新的疑修指派给我", key: "IssueAssigned") self.find_or_create_by(name: "有新的合并请求指派给我", key: "PullRequestAssigned") end end diff --git a/app/models/user.rb b/app/models/user.rb index 8a05a056..c71a0249 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -786,7 +786,7 @@ class User < Owner end def profile_is_completed? - self.nickname.present? && self.gender.present? && self.mail.present? && self.custom_department.present? + self.nickname.present? && self.mail.present? end protected diff --git a/app/services/atme_service.rb b/app/services/atme_service.rb index 623b32c0..f93314e4 100644 --- a/app/services/atme_service.rb +++ b/app/services/atme_service.rb @@ -32,6 +32,6 @@ class AtmeService < ApplicationService else return end - SendTemplateMessageJob.perform_now(message_source, receivers, user.id, @atmeable.id) if Site.has_notice_menu? + SendTemplateMessageJob.perform_later(message_source, receivers.pluck(:id), user.id, @atmeable.id) if Site.has_notice_menu? end end diff --git a/app/services/gitea/organization/update_service.rb b/app/services/gitea/organization/update_service.rb index c04dcbc8..963099ad 100644 --- a/app/services/gitea/organization/update_service.rb +++ b/app/services/gitea/organization/update_service.rb @@ -16,6 +16,7 @@ class Gitea::Organization::UpdateService < Gitea::ClientService def request_params update_params = { name: org.login, + full_name: org.nickname, description: org.description, location: org.location, repo_admin_change_team_access: org.repo_admin_change_team_access, diff --git a/app/services/gitea/repository/branches/list_name_service.rb b/app/services/gitea/repository/branches/list_name_service.rb new file mode 100644 index 00000000..716ee464 --- /dev/null +++ b/app/services/gitea/repository/branches/list_name_service.rb @@ -0,0 +1,22 @@ +class Gitea::Repository::Branches::ListNameService < Gitea::ClientService + attr_reader :user, :repo + + def initialize(user, repo) + @user = user + @repo = repo + end + + def call + response = get(url, params) + render_200_response(response) + end + + private + def params + Hash.new.merge(token: user.gitea_token) + end + + def url + "/repos/#{user.login}/#{repo}/branch_name_set".freeze + end +end diff --git a/app/services/gitea/repository/entries/create_service.rb b/app/services/gitea/repository/entries/create_service.rb index 5e1a4c4b..1bdc7246 100644 --- a/app/services/gitea/repository/entries/create_service.rb +++ b/app/services/gitea/repository/entries/create_service.rb @@ -55,7 +55,12 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService when 201 then success(json_parse!(body)) when 403 then error("你没有权限操作!") when 404 then error("你操作的链接不存在!") - when 422 then error("#{filepath}文件已存在,不能重复创建!") + when 422 + if @body[:new_branch].include?('/') || @body[:new_branch].include?('\'') || @body[:new_branch].include?('^') || @body[:new_branch].include?('*') + error("不合法的分支名称!") + else + error("#{filepath}文件已存在,不能重复创建!") + end else error("系统错误!") end end diff --git a/app/services/notice/write/client_service.rb b/app/services/notice/write/client_service.rb index c76691cf..d9dc841f 100644 --- a/app/services/notice/write/client_service.rb +++ b/app/services/notice/write/client_service.rb @@ -59,6 +59,8 @@ class Notice::Write::ClientService < ApplicationService req.request :url_encoded req.headers['Content-Type'] = 'application/json' req.adapter Faraday.default_adapter + req.options.timeout = 100 # open/read timeout in seconds + req.options.open_timeout = 10 # connection open timeout in seconds end end diff --git a/app/services/pull_requests/create_service.rb b/app/services/pull_requests/create_service.rb index cc7493ea..4b814fd3 100644 --- a/app/services/pull_requests/create_service.rb +++ b/app/services/pull_requests/create_service.rb @@ -129,8 +129,10 @@ 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]) if pull_request.is_original && @params[:merge_user_login] - base_pull_params.merge(head: "#{@params[:merge_user_login]}:#{@params[:head]}") + base_pull_params.merge(head: "#{@params[:merge_user_login]}:#{base_pull_params[:head]}") else base_pull_params end @@ -148,6 +150,7 @@ class PullRequests::CreateService < ApplicationService def validate! raise "title参数不能为空" if @params[:title].blank? + raise "title不能超过255个字符" if @params[:title].length > 255 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) @@ -159,7 +162,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, @params[:base], head, @current_user.gitea_token) + compare_result = Gitea::Repository::Commits::CompareService.call(@owner.login, @project.identifier, CGI.escape(@params[:base]), CGI.escape(head), @current_user.gitea_token) raise '分支内容相同,无需创建合并请求' if compare_result["Commits"].blank? && compare_result["Diff"].blank? end diff --git a/app/services/repositories/detail_service.rb b/app/services/repositories/detail_service.rb index b2e7a69e..b39b408c 100644 --- a/app/services/repositories/detail_service.rb +++ b/app/services/repositories/detail_service.rb @@ -10,20 +10,12 @@ class Repositories::DetailService < ApplicationService def call return { repo: repo_suitable, - contributor: contributor_suitable, - language: language_suitable, branch_tag_total_count: branch_tag_total_count } rescue return { repo: {}, - release: [], - branch: [], - branch_type: [], - tag: [], - contributor: [], - language: {}, - readme: {} + branch_tag_total_count: {} } end @@ -35,14 +27,4 @@ class Repositories::DetailService < ApplicationService def repo_suitable Gitea::Repository::GetService.call(@owner, @repo.identifier) end - - def contributor_suitable - contributors = Gitea::Repository::Contributors::GetService.call(@owner, @repo.identifier) - contributors.is_a?(Hash) && contributors.key?(:status) ? [] : contributors - end - - def language_suitable - result = Gitea::Repository::Languages::ListService.call(@owner.login, @repo.identifier, @user&.gitea_token) - result[:status] === :success ? hash_transform_precentagable(result[:body]) : nil - end end diff --git a/app/views/claims/_claim_item.json.jbuilder b/app/views/claims/_claim_item.json.jbuilder new file mode 100644 index 00000000..67a8e472 --- /dev/null +++ b/app/views/claims/_claim_item.json.jbuilder @@ -0,0 +1,7 @@ +json.user_name claimer.user.try(:show_real_name) +json.user_login claimer.user.try(:login) +json.user_picture url_to_avatar(claimer.user) +json.created_at time_from_now(claimer.created_at) +json.note_body claimer.note +json.claim_id claimer.id +json.visible false \ No newline at end of file diff --git a/app/views/claims/list.json.jbuilder b/app/views/claims/list.json.jbuilder new file mode 100644 index 00000000..b5fef612 --- /dev/null +++ b/app/views/claims/list.json.jbuilder @@ -0,0 +1,7 @@ +json.partial! "commons/success" +json.currentUserclaimed @user_claimed +json.claimers do + json.array! @claims do |claimer| + json.partial! "claims/claim_item", claimer: claimer + end +end \ No newline at end of file diff --git a/app/views/issues/show.json.jbuilder b/app/views/issues/show.json.jbuilder index be6de14e..5c8cbc41 100644 --- a/app/views/issues/show.json.jbuilder +++ b/app/views/issues/show.json.jbuilder @@ -13,6 +13,7 @@ json.tracker @issue.tracker.try(:name) json.issue_status @issue.issue_status.try(:name) json.priority @issue.priority.try(:name) json.version @issue.version.try(:name) +json.version_id @issue.version.try(:id) json.issue_tags @issue.get_issue_tags json.done_ratio @issue.done_ratio.to_s + "%" json.issue_type @issue.issue_type diff --git a/app/views/organizations/teams/_simple_detail.json.jbuilder b/app/views/organizations/teams/_simple_detail.json.jbuilder new file mode 100644 index 00000000..5e9a6246 --- /dev/null +++ b/app/views/organizations/teams/_simple_detail.json.jbuilder @@ -0,0 +1,3 @@ +json.id team.id +json.name team.name +json.nickname team.nickname.blank? ? team.name : team.nickname \ No newline at end of file diff --git a/app/views/organizations/teams/index.json.jbuilder b/app/views/organizations/teams/index.json.jbuilder index ad3479ec..189047fc 100644 --- a/app/views/organizations/teams/index.json.jbuilder +++ b/app/views/organizations/teams/index.json.jbuilder @@ -1,4 +1,8 @@ -json.total_count @teams.total_count +json.total_count params[:is_full].present? ? @teams.count : @teams.total_count json.teams @teams do |team| - json.partial! "detail", team: team, organization: @organization + if params[:is_full].present? + json.partial! "simple_detail", team: team, organization: @organization + else + json.partial! "detail", team: team, organization: @organization + end end diff --git a/app/views/project_rank/_detail.json.jbuilder b/app/views/project_rank/_detail.json.jbuilder index b2340396..5e45f518 100644 --- a/app/views/project_rank/_detail.json.jbuilder +++ b/app/views/project_rank/_detail.json.jbuilder @@ -3,15 +3,26 @@ owner_common = $redis_cache.hgetall("v2-owner-common:#{project_common["owner_id" json.id item[0] json.score item[1] json.name project_common["name"] -json.identifier project_common["identifier"] -json.description project_common["description"] -json.owner do - json.id project_common["owner_id"] - json.type owner_common["type"] - json.name owner_common["name"] - json.login owner_common["login"] - json.avatar_url owner_common["avatar_url"] +if project_common['identifier'].include?("/") + json.identifier project_common["identifier"].split('/')[1] + json.owner do + json.id nil + json.type 'User' + json.name project_common["identifier"].split('/')[0] + json.login project_common["identifier"].split('/')[0] + json.avatar_url User::Avatar.get_letter_avatar_url(project_common["identifier"].split('/')[0]) + end +else + json.identifier project_common["identifier"] + json.owner do + json.id project_common["owner_id"] + json.type owner_common["type"] + json.name owner_common["name"] + json.login owner_common["login"] + json.avatar_url owner_common["avatar_url"] + end end +json.description project_common["description"] json.visits project_common["visits"] json.forks project_common["forks"] json.watchers project_common["watchers"] diff --git a/app/views/project_trends/_detail.json.jbuilder b/app/views/project_trends/_detail.json.jbuilder index 75bcf223..04de10f6 100644 --- a/app/views/project_trends/_detail.json.jbuilder +++ b/app/views/project_trends/_detail.json.jbuilder @@ -6,6 +6,13 @@ json.user_name trend.user.try(:show_real_name) json.user_login trend.user.login json.user_avatar url_to_avatar(trend.user) json.action_time time_from_now(trend.created_at) +json.project do + json.owner do + json.partial! 'users/user_simple', locals: {user: trend&.project&.owner} + end + json.identifier trend&.project&.identifier + json.description trend&.project&.description +end if trend.trend_type == "Issue" json.partial! "issues/simple_issue_item", locals: {issue: trend.trend} diff --git a/app/views/projects/branches.json.jbuilder b/app/views/projects/branches.json.jbuilder index c7c2025b..3bfc9f46 100644 --- a/app/views/projects/branches.json.jbuilder +++ b/app/views/projects/branches.json.jbuilder @@ -1,21 +1,22 @@ json.array! @branches do |branch| - json.name branch['name'] - json.user_can_push branch['user_can_push'] - json.user_can_merge branch['user_can_merge'] - json.protected branch['protected'] + branch_name = branch.is_a?(Hash) ? branch['name'] : branch + json.name branch_name + # json.user_can_push branch['user_can_push'] + # json.user_can_merge branch['user_can_merge'] + # json.protected branch['protected'] json.http_url render_http_url(@project) - json.zip_url render_zip_url(@owner, @repository, branch['name']) - json.tar_url render_tar_url(@owner, @repository, branch['name']) - json.last_commit do - json.sha branch['commit']['id'] - json.message branch['commit']['message'] - json.timestamp render_unix_time(branch['commit']['timestamp']) - json.time_from_now time_from_now(branch['commit']['timestamp']) - json.author do - json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['author']), name: branch['commit']['author']['name'] - end - json.committer do - json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['committer']), name: branch['commit']['committer']['name'] - end - end + json.zip_url render_zip_url(@owner, @repository, branch_name) + json.tar_url render_tar_url(@owner, @repository, branch_name) + # json.last_commit do + # json.sha branch['commit']['id'] + # json.message branch['commit']['message'] + # json.timestamp render_unix_time(branch['commit']['timestamp']) + # json.time_from_now time_from_now(branch['commit']['timestamp']) + # json.author do + # json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['author']), name: branch['commit']['author']['name'] + # end + # json.committer do + # json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['committer']), name: branch['commit']['committer']['name'] + # end + # end end diff --git a/app/views/projects/webhooks/_detail.json.jbuilder b/app/views/projects/webhooks/_detail.json.jbuilder index 2497e5c6..41a91f4a 100644 --- a/app/views/projects/webhooks/_detail.json.jbuilder +++ b/app/views/projects/webhooks/_detail.json.jbuilder @@ -1,4 +1,4 @@ json.(webhook, :id, :url, :http_method, :is_active) -json.type webhook.hook_task_type +json.type webhook.type json.last_status webhook.last_status json.create_time Time.at(webhook.created_unix).strftime("%Y-%m-%d %H:%M:%S") \ No newline at end of file diff --git a/app/views/projects/webhooks/edit.json.jbuilder b/app/views/projects/webhooks/edit.json.jbuilder index 2ee6d24e..c54d1030 100644 --- a/app/views/projects/webhooks/edit.json.jbuilder +++ b/app/views/projects/webhooks/edit.json.jbuilder @@ -1,6 +1,6 @@ json.id @webhook.id json.(@webhook, :id, :http_method, :content_type, :url, :secret, :last_status, :is_active) -json.type @webhook.hook_task_type +json.type @webhook.type json.create_time Time.at(@webhook.created_unix).strftime("%Y-%m-%d %H:%M:%S") event = @webhook.events json.branch_filter event["branch_filter"] diff --git a/app/views/projects/webhooks/tasks.json.jbuilder b/app/views/projects/webhooks/tasks.json.jbuilder index b8aef99f..82b2eae4 100644 --- a/app/views/projects/webhooks/tasks.json.jbuilder +++ b/app/views/projects/webhooks/tasks.json.jbuilder @@ -1,5 +1,6 @@ json.total_count @tasks.total_count json.tasks @tasks.each do |task| - json.(task, :id, :type, :uuid, :is_succeed, :is_delivered, :payload_content, :request_content, :response_content) + json.(task, :id, :event_type, :type, :uuid, :is_succeed, :is_delivered, :payload_content, :request_content) + json.response_content task.response_content_json json.delivered_time Time.at(task.delivered*10**-9).strftime("%Y-%m-%d %H:%M:%S") end \ No newline at end of file diff --git a/app/views/pull_requests/_commit.json.jbuilder b/app/views/pull_requests/_commit.json.jbuilder index 52b77600..2cd2bab7 100644 --- a/app/views/pull_requests/_commit.json.jbuilder +++ b/app/views/pull_requests/_commit.json.jbuilder @@ -1,12 +1,27 @@ -json.author do - json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['Author']), name: commit['Author']['Name'] } -end +if commit['Status'].present? + json.author do + json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['Author']), name: commit['Author']['Name'] } + end + + json.committer do + json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['Committer']), name: commit['Committer']['Name'] } + end + json.timestamp render_unix_time(commit['Committer']['When']) + json.time_from_now time_from_now(commit['Committer']['When']) + json.created_at render_format_time_with_date(commit['Committer']['When']) + json.message commit['CommitMessage'] + json.sha commit['Sha'] +else + json.author do + json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['commit']['author']), name: commit['commit']['author']['name'] } + end -json.committer do - json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['Committer']), name: commit['Committer']['Name'] } -end -json.timestamp render_unix_time(commit['Committer']['When']) -json.time_from_now time_from_now(commit['Committer']['When']) -json.created_at render_format_time_with_date(commit['Committer']['When']) -json.message commit['CommitMessage'] -json.sha commit['Sha'] + json.committer do + json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['commit']['committer']), name: commit['commit']['committer']['name'] } + end + json.timestamp render_unix_time(commit['commit']['committer']['date']) + json.time_from_now time_from_now(commit['commit']['committer']['date']) + json.created_at render_format_time_with_date(commit['commit']['committer']['date']) + json.message commit['commit']['message'] + json.sha commit['sha'] +end \ No newline at end of file diff --git a/app/views/repositories/_commit.json.jbuilder b/app/views/repositories/_commit.json.jbuilder index 9c29116e..892353ea 100644 --- a/app/views/repositories/_commit.json.jbuilder +++ b/app/views/repositories/_commit.json.jbuilder @@ -7,11 +7,22 @@ if @project.educoder? json.timestamp 0 json.time_from_now commit[0]['time'] end - json.author do - {} - # json.partial! '/projects/author', user: render_commit_author(commit['author']) - end - json.committer {} + if commit[0]['author'].present? + json.author do + json.id nil + json.login commit[0]['author']['username'] + json.name commit[0]['author']['username'] + json.type nil + json.image_url commit[0]['author']['image_url'] + end + json.committer do + json.id nil + json.login commit[0]['author']['username'] + json.name commit[0]['author']['username'] + json.type nil + json.image_url commit[0]['author']['image_url'] + end + end end if @project.forge? diff --git a/app/views/repositories/_simple_entry.json.jbuilder b/app/views/repositories/_simple_entry.json.jbuilder index 6f33ac35..80a54d47 100644 --- a/app/views/repositories/_simple_entry.json.jbuilder +++ b/app/views/repositories/_simple_entry.json.jbuilder @@ -32,6 +32,8 @@ end if @project.educoder? file_path = params[:filepath].present? ? [params[:filepath], entry['name']].join('/') : entry['name'] + file_type = File.extname(entry['name'].to_s)[1..-1] + image_type = image_type?(file_type) json.name entry['name'] json.sha nil @@ -42,7 +44,7 @@ if @project.educoder? json.target nil json.download_url nil json.direct_download false - json.image_type false + json.image_type image_type json.is_readme_file false json.commit do json.message entry['title'] diff --git a/app/views/repositories/commits.json.jbuilder b/app/views/repositories/commits.json.jbuilder index 33406e6a..14b9f1b9 100644 --- a/app/views/repositories/commits.json.jbuilder +++ b/app/views/repositories/commits.json.jbuilder @@ -1,6 +1,26 @@ -if @hash_commit.blank? #如果有状态值,则表示报错了 +if @hash_commit.blank? || @project.educoder? #如果有状态值,则表示报错了 json.total_count 0 - json.commits [] + json.commits do + json.array! @commits do |commit| + json.sha commit['id'] + json.message commit['title'] + json.time_from_now commit['time'] + json.author do + json.id nil + json.login commit['author']['username'] + json.name commit['author']['username'] + json.type nil + json.image_url commit['author']['image_url'] + end + json.committer do + json.id nil + json.login commit['author']['username'] + json.name commit['author']['username'] + json.type nil + json.image_url commit['author']['image_url'] + end + end + end else json.total_count @hash_commit[:total_count] json.commits do diff --git a/app/views/repositories/contributors.json.jbuilder b/app/views/repositories/contributors.json.jbuilder index fa52475a..2fb6abae 100644 --- a/app/views/repositories/contributors.json.jbuilder +++ b/app/views/repositories/contributors.json.jbuilder @@ -1,5 +1,5 @@ total_count = @contributors.size -json.contributors @contributors.each do |contributor| +json.list @contributors.each do |contributor| json.partial! 'contributor', locals: { contributor: contributor } end json.total_count total_count diff --git a/app/views/repositories/detail.json.jbuilder b/app/views/repositories/detail.json.jbuilder index b741e01f..9381c473 100644 --- a/app/views/repositories/detail.json.jbuilder +++ b/app/views/repositories/detail.json.jbuilder @@ -44,7 +44,7 @@ end if @result[:repo] json.size replace_bytes_to_b(number_to_human_size(@result[:repo]['size'].to_i*1024)) json.ssh_url @result[:repo]['ssh_url'] - json.clone_url @result[:repo]['clone_url'] + json.clone_url @project.educoder? ? "#{Rails.application.config_for(:configuration)['educoder']['git_site']}/#{@project&.project_educoder&.repo_name}.git" : @result[:repo]['clone_url'] json.default_branch @project.educoder? ? "master" : @result[:repo]['default_branch'] json.empty @result[:repo]['empty'] json.full_name @result[:repo]['full_name'] @@ -53,13 +53,13 @@ end json.license_name @project.license_name json.branches_count @result[:branch_tag_total_count].present? ? (@result[:branch_tag_total_count]['branch_count'] || 0) : 0 json.tags_count @result[:branch_tag_total_count].present? ? (@result[:branch_tag_total_count]['tag_count'] || 0) : 0 -json.contributors do - total_count = @result[:contributor].size - json.list @result[:contributor].each do |contributor| - json.partial! 'contributor', locals: { contributor: contributor } - end - json.total_count total_count -end -json.languages @result[:language].blank? ? nil : @result[:language] +# json.contributors do +# total_count = @result[:contributor].size +# json.list @result[:contributor].each do |contributor| +# json.partial! 'contributor', locals: { contributor: contributor } +# end +# json.total_count total_count +# end +# json.languages @result[:language].blank? ? nil : @result[:language] json.partial! 'author', locals: { user: @project.owner } diff --git a/app/views/repositories/entries.json.jbuilder b/app/views/repositories/entries.json.jbuilder index 8d1e67be..6dea93b3 100644 --- a/app/views/repositories/entries.json.jbuilder +++ b/app/views/repositories/entries.json.jbuilder @@ -7,7 +7,7 @@ if @project.educoder? end end json.commits_count @entries['commit_count'] - json.zip_url @entries['git_url'] + json.zip_url '' json.tar_url '' json.entries do json.array! @entries['trees'] do |entry| diff --git a/app/views/repositories/sub_entries.json.jbuilder b/app/views/repositories/sub_entries.json.jbuilder index 3276ed0d..296e307f 100644 --- a/app/views/repositories/sub_entries.json.jbuilder +++ b/app/views/repositories/sub_entries.json.jbuilder @@ -24,8 +24,12 @@ if @project.educoder? end end json.entries do - json.array! @sub_entries['trees'] do |entry| - json.partial! 'repositories/simple_entry', locals: { entry: entry } + if @sub_entries['trees'].is_a?(Array) + json.array! @sub_entries['trees'] do |entry| + json.partial! 'repositories/simple_entry', locals: { entry: entry } + end + elsif @sub_entries['trees'].is_a?(Hash) + json.partial! 'repositories/simple_entry', locals: { entry: @sub_entries['trees'] } end end end diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index bed2d1e6..62ea1696 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -15,7 +15,7 @@ zh-CN: 'refused': '已拒绝' 'agreed': '已同意' trend: - Issue: 易修(Issue) + Issue: 疑修(Issue) PullRequest: 合并请求(PR) VersionRelease: 版本发布 create: 创建了 diff --git a/config/routes.rb b/config/routes.rb index 4942bcfe..36cd5391 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,7 @@ Rails.application.routes.draw do # Serve websocket cable requests in-process mount ActionCable.server => '/cable' + get 'attachments/entries/get_file', to: 'attachments#get_file' get 'attachments/download/:id', to: 'attachments#show' get 'attachments/download/:id/:filename', to: 'attachments#show' @@ -146,6 +147,16 @@ Rails.application.routes.draw do get :get_children_journals end end + + resources :claims, only: [:index] do + collection do + post :create + delete :destroy + get :index + put :update + end + end + resources :issue_times, only: [:create] do collection do post :end_work @@ -214,6 +225,7 @@ Rails.application.routes.draw do get :watch_users get :fan_users get :hovercard + put :update_image end collection do post :following diff --git a/db/migrate/20211115014133_create_claims.rb b/db/migrate/20211115014133_create_claims.rb new file mode 100644 index 00000000..f7910243 --- /dev/null +++ b/db/migrate/20211115014133_create_claims.rb @@ -0,0 +1,11 @@ +class CreateClaims < ActiveRecord::Migration[5.2] + def change + create_table :claims do |t| + t.integer :issue_id + t.integer :user_id + t.timestamps + t.index :issue_id + t.index :user_id + end + end +end diff --git a/db/migrate/20211119135526_add_note_to_claim.rb b/db/migrate/20211119135526_add_note_to_claim.rb new file mode 100644 index 00000000..9a1a8e96 --- /dev/null +++ b/db/migrate/20211119135526_add_note_to_claim.rb @@ -0,0 +1,5 @@ +class AddNoteToClaim < ActiveRecord::Migration[5.2] + def change + add_column :claims, :note, :text + end +end diff --git a/db/migrate/20211222015006_add_team_user_to_members.rb b/db/migrate/20211222015006_add_team_user_to_members.rb new file mode 100644 index 00000000..b3ca8edf --- /dev/null +++ b/db/migrate/20211222015006_add_team_user_to_members.rb @@ -0,0 +1,5 @@ +class AddTeamUserToMembers < ActiveRecord::Migration[5.2] + def change + add_reference :members, :team_user + end +end diff --git a/dir.md b/dir.md index a4443ded..1d3c35e3 100644 --- a/dir.md +++ b/dir.md @@ -87,7 +87,7 @@ forgeplus │ │ │ │ └── tags(标签) │ │ │ ├── user(用户) │ │ │ └── versions(发行版) -│ │ ├── issues(易修) +│ │ ├── issues(疑修) │ │ ├── libraries │ │ ├── oauth │ │ ├── organizations(组织) diff --git a/public/assets/.sprockets-manifest-7657344e1d61e579de6a996a4498d7a2.json b/public/assets/.sprockets-manifest-7657344e1d61e579de6a996a4498d7a2.json index 4e20baac..1cdf5b08 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="}},"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 +{"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 diff --git a/public/assets/admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js b/public/assets/admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js new file mode 100644 index 00000000..d7be53fe --- /dev/null +++ b/public/assets/admin-445f2dc5f92048f7bcdcec94bc4a7fcbd5d077e79e5b648c8d742e0531cc6ffa.js @@ -0,0 +1,49 @@ +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='