class ShixunsController < ApplicationController before_action :require_login, except: [:download_file, :index, :menus] before_action :check_auth, except: [:download_file, :index] before_action :find_shixun, except: [:index, :new, :create, :menus, :get_recommend_shixuns, :propaedeutics, :departments, :apply_shixun_mirror, :get_mirror_script, :download_file] before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy] before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish, :shixun_members_added, :change_manager, :collaborators_delete, :cancel_publish, :add_collaborators] before_action :portion_allowed, only: [:copy] before_action :special_allowed, only: [:send_to_course, :search_user_courses] include ShixunsHelper include ApplicationHelper ## 获取课程列表 def index ## 我的实训 @shixuns = if params[:order_by] == 'mine' current_user.my_shixuns else Shixun.unhidden end ## 方向 if params[:tag_level].present? && params[:tag_id].present? @shixuns = @shixuns.filter_tag(params[:tag_level].to_i, params[:tag_id].to_i) case params[:tag_level].to_i when 1 #大类 @search_tags = Repertoire.find(params[:tag_id].to_i).name when 2 #子类 @search_tags = SubRepertoire.find(params[:tag_id].to_i).name when 3 #tag tag = TagRepertoire.find(params[:tag_id].to_i) @search_tags = "#{tag.sub_repertoire.name} / #{tag.name}" end end ## 搜索关键字 匹配关卡名, 用户名, 实训名 和 空格多搜索 if params[:keyword].present? keyword = params[:keyword].strip @shixuns = @shixuns.joins(:users, challenges: :challenge_tags). where("challenge_tags.name like '%#{keyword}%' or challenges.subject like '%#{keyword}%' or concat(lastname, firstname) like '%#{keyword}%' or shixuns.name like '%#{keyword.split(" ").join("%")}%'") end ## 筛选 状态 if params[:status].present? && params[:status].to_i != 0 params[:status] = [0, 1] if params[:status].to_i == 1 @shixuns = @shixuns.where(status: params[:status]) end ## 筛选 难度 if params[:diff].present? && params[:diff].to_i != 0 @shixuns = @shixuns.where(trainee: params[:diff]) end ## 排序参数 bsort = params[:sort] || 'desc' case params[:order_by] || 'publish_time' when 'new' @shixuns = @shixuns.order("shixuns.status = 2 desc, shixuns.created_at #{bsort}") when 'hot' @shixuns = @shixuns.order("shixuns.status = 2 desc, myshixuns_count #{bsort}") when 'mine' @shixuns = @shixuns.order("shixuns.created_at #{bsort}") else @shixuns = @shixuns.order("shixuns.status = 2 desc, publish_time #{bsort}") end @total_count = @shixuns.count ## 分页参数 page = params[:page] || 1 limit = params[:limit] || 16 @shixuns = @shixuns.includes(:tag_repertoires, :challenges).page(page).per(limit) end ## 获取顶部菜单 def menus @repertoires = Repertoire.includes(sub_repertoires: [:tag_repertoires]).order("updated_at asc") # respond_with @repertoires render_json end ## 实训详情 def show # 当前用户开启的实训 can_fork = current_user.is_certification_teacher || current_user.admin? unless can_fork @can_fork = {can_fork: "已经职业认证的教师才能fork实训", certi_url: edu_setting('old_edu_host') + "/account/professional_certification"} end @current_myshixun = @shixun.current_myshixun(current_user) if @shixun.fork_from fork_shixun = Shixun.select(:id, :user_id, :name, :identifier).where(id: @shixun.fork_from).first @fork_from = {name: fork_shixun.name, username: fork_shixun.owner.try(:full_name), fork_identifier: fork_shixun.identifier} if fork_shixun end @power = current_user.manager_of_shixun?(@shixun) # 更新是为了首页的排序,myshixun的动静需要在实训中展现出来 if @current_myshixun && params[:exit] && !current_user.admin? @current_myshixun.update_column(:updated_at, Time.now) end end def show_right owner = @shixun.owner #@fans_count = owner.fan_count #@followed_count = owner.follow_count @user_own_shixuns = owner.shixuns.published.count end # 排行榜 def ranking_list if @shixun.status == 2 sql = " select m.user_id, u.login, u.lastname, m.updated_at, (select sum(cost_time) from games g where g.myshixun_id = m.id) as time, (select sum(final_score) from games g where g.myshixun_id = m.id) as score from (myshixuns m join users u on m.user_id = u.id) where m.shixun_id = #{@shixun.id} and m.status = 1 order by score desc, time asc limit 10 " @myshixuns = Myshixun.find_by_sql(sql) end end #评论 def discusses new_params = params.merge(container_id: @shixun.id, container_type: 'Shixun') discusses = ShixunsService.new.shixun_discuss new_params, current_user if discusses.present? @children_list = discusses[:children_list] else @children_list = [] end end def copy ActiveRecord::Base.transaction do begin @new_shixun = Shixun.new @new_shixun.attributes = @shixun.attributes.dup.except("id","user_id","visits","gpid","status", "identifier", "averge_star", "homepage_show","repo_name", "myshixuns_count", "challenges_count", "can_copy") @new_shixun.user_id = User.current.id @new_shixun.averge_star = 5 @new_shixun.identifier = generate_identifier Shixun, 8 @new_shixun.fork_from = @shixun.id @new_shixun.save! # 同步shixun_info的信息 if @shixun.shixun_info.present? ShixunInfo.create!(shixun_id: @new_shixun.id, description: @shixun.description, evaluate_script: @shixun.evaluate_script) end # 同步镜像 if @shixun.mirror_repositories.present? @shixun.mirror_repositories.each do |mirror| ShixunMirrorRepository.create!(:shixun_id => @new_shixun.id, :mirror_repository_id => mirror.id) end end # 同步技术标签 @shixun.shixun_tag_repertoires.each do |str| ShixunTagRepertoire.create!(:tag_repertoire_id => str.tag_repertoire_id, :shixun_id => @new_shixun.id) end # fork版本库 logger.info("###########fork_repo_path: ######{@repo_path}") project_fork(@new_shixun, @repo_path, current_user.login) ShixunMember.create!(:user_id => User.current.id, :shixun_id => @new_shixun.try(:id), :role => 1) # 同步复制关卡 if @shixun.challenges.present? @shixun.challenges.each do |challenge| new_challenge = Challenge.new new_challenge.attributes = challenge.attributes.dup.except("id","shixun_id","user_id", "challenge_tags_count") new_challenge.user_id = User.current.id new_challenge.shixun_id = @new_shixun.id new_challenge.save! if challenge.st == 0 # 评测题 # 同步测试集 if challenge.test_sets.present? challenge.test_sets.each do |test_set| new_test_set = TestSet.new new_test_set.attributes = test_set.attributes.dup.except("id","challenge_id") new_test_set.challenge_id = new_challenge.id new_test_set.save! end end # 同步关卡标签 challenge_tags = ChallengeTag.where("challenge_id =? and challenge_choose_id is null", challenge.id) if challenge_tags.present? challenge_tags.each do |challenge_tag| ChallengeTag.create!(:challenge_id => new_challenge.id, :name => challenge_tag.try(:name)) end end elsif challenge.st == 1 # 选择题 if challenge.challenge_chooses.present? challenge.challenge_chooses.each do |challenge_choose| new_challenge_choose = ChallengeChoose.new new_challenge_choose.attributes = challenge_choose.attributes.dup.except("id","challenge_id") new_challenge_choose.challenge_id = new_challenge.id new_challenge_choose.save! # 每一题的选项 if challenge_choose.challenge_questions.present? challenge_choose.challenge_questions.each do |challenge_question| new_challenge_question = ChallengeQuestion.new new_challenge_question.attributes = challenge_question.attributes.dup.except("id","challenge_choose_id") new_challenge_question.challenge_choose_id = new_challenge_choose.id new_challenge_question.save! end end # 每一题的知识标签 st_challenge_tags = ChallengeTag.where(:challenge_id => challenge.id, :challenge_choose_id => challenge_choose.id) if st_challenge_tags.present? st_challenge_tags.each do |st_challenge_tag| ChallengeTag.create!(:challenge_id => new_challenge.id, :name => st_challenge_tag.try(:name), :challenge_choose_id => new_challenge_choose.id) end end end end end end end rescue Exception => e uid_logger_error("copy shixun failed ##{e.message}") g.delete_project(gshixungshixun.id) if gshixun.try(:id).present? # 异常后,如果已经创建了版本库需要删除该版本库 tip_exception("实训Fork失败") raise ActiveRecord::Rollback end end end #合作者 def collaborators @user = current_user @members = @shixun.shixun_members.includes(:user) end def fork_list @shixuns = Shixun.where(:fork_from => @shixun.id) @shixuns_count = @shixuns.count ## 分页参数 page = params[:page] || 1 limit = params[:limit] || 20 @shixuns = @shixuns.page(page).per(limit) end def new @introduction_sample = PlatformSample.where(samples_type: ['introduction', 'knowledge']).pluck([:samples_type, :contents]) @main_type = shixun_main_type @small_type = shixun_small_type end # 注意这里传参都应该使用params[:shixun]['name']这种格式 def create # 评测脚本的一些操作 main_type, sub_type = params[:main_type], params[:small_type] mirror = MirrorScript.where(:mirror_repository_id => main_type) identifier = generate_identifier Shixun, 8 @shixun = Shixun.new(shixun_params) @shixun.identifier = identifier @shixun.user_id = current_user.id @shixun.reset_time, @shixun.modify_time = Time.now, Time.now if sub_type.blank? shixun_script = mirror.first.try(:script) else main_mirror = MirrorRepository.find(main_type).type_name sub_mirror = MirrorRepository.where(id: sub_type).pluck(:type_name) if main_mirror == "Java" && sub_mirror.include?("Mysql") shixun_script = mirror.last.try(:script) else shixun_script = mirror.first.try(:script) shixun_script = modify_shixun_script @shixun, shixun_script end end ActiveRecord::Base.transaction do begin @shixun.save! # shixun_info关联ß ShixunInfo.create!(shixun_id: @shixun.id, evaluate_script: shixun_script, description: params[:description]) # 实训的公开范围 if params[:scope_partment].present? arr = [] ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq ids.each do |id| arr << { :school_id => id, :shixun_id => @shixun.id } end ShixunSchool.create!(arr) end # 实训合作者 @shixun.shixun_members.create!(user_id: current_user.id, role: 1) # 镜像-实训关联表 ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => main_type.to_i) if main_type.present? if sub_type.present? sub_type.each do |mirror| ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror) end end # 创建版本库 repo_path = repo_namespace(User.current.login, @shixun.identifier) GitService.add_repository(repo_path: repo_path) # todo: 为什么保存的时候要去除后面的.git呢?? @shixun.update_column(:repo_name, repo_path.split(".")[0]) rescue Exception => e uid_logger_error(e.message) tip_exception("实训创建失败") raise ActiveRecord::Rollback end end end def apply_shixun_mirror form_params = params.permit(*%i[language runtime run_method attachment_id]) form = ApplyShixunMirrorForm.new(form_params) form.validate! tiding = Tiding.new( user_id: 1, trigger_user_id: current_user.id, container_type: 'SendMessage', viewed: 0, tiding_type: 'Apply', extra: form.to_json ) ActiveRecord::Base.transaction do # TODO: 由于tiding是多态,而SendMessage却没有对应的model,因此,如果不跳过验证会报 container must exist的错. tiding.save(validate: false) form.attachment.update!(container: tiding) end sucess_status rescue ActiveModel::ValidationError => ex tip_exception(ex.message) rescue Exception => e uid_logger_error(e.message) tip_exception("申请失败") end def update h = {test_set_permission: params[:test_set_permission], code_hidden: params[:code_hidden], task_pass: params[:task_pass], hide_code: params[:hide_code], forbid_copy: params[:forbid_copy]} s_params = shixun_params.merge(h) ActiveRecord::Base.transaction do begin @shixun.shixun_mirror_repositories.destroy_all if params[:main_type].present? ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => params[:main_type].to_i) end if params[:small_type].present? params[:small_type].each do |mirror| ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => mirror) end end @shixun.update_attributes(s_params) @shixun.shixun_info.update_attributes(description: params[:description], evaluate_script: params[:evaluate_script]) @shixun.shixun_schools.delete_all if params[:scope_partment].present? && params[:user_scope].to_i == 1 arr = [] ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq ids.each do |id| arr << { :school_id => id, :shixun_id => @shixun.id } end ShixunSchool.create!(arr) use_scope = 1 else use_scope = 0 end @shixun.update_attributes!(:use_scope => use_scope) rescue Exception => e uid_logger_error(e.message) tip_exception("实训保存失败") raise ActiveRecord::Rollback end end end # 永久关闭实训 def close @shixun.update_attributes(status: 3, closer_id: current_user.id, end_time: Time.now) sucess_status end def propaedeutics @content = Shixun.find_by_identifier!(params[:identifier]).propaedeutics end # 更新背景知识 def update_propaedeutics @shixun.shixun_info.update_column(:propaedeutics, params[:content]) end # 获取推荐实训接口 2个热门实训 + 2个最新实训 def get_recommend_shixuns hot_shixuns = Shixun.field_for_recommend.published.order("myshixuns_count desc").limit(2) newest_shixuns = Shixun.field_for_recommend.published.order("created_at desc").limit(2) @recommend_shixuns = hot_shixuns + newest_shixuns end def settings @choice_main_type = @shixun.main_mirror_id @choice_small_type = @shixun.small_mirror_id @main_type = shixun_main_type @small_type = shixun_small_type #@mirror_script = MirrorScript.select([:id, :script_type]).find(@shixun.mirror_script_id).attributes if @shixun.mirror_script_id && @shixun.mirror_script_id != 0 # @shixun_main_mirror = @shixun.show_shixun_mirror # @script_type = @shixun.script_tag.try(:script_type) || "无" # @evaluate_scirpt = @shixun.evaluate_script || "无" end # 获取脚本内容 def get_script_contents mirrir_script = MirrorScript.find(params[:script_id]) script = mirrir_script.try(:script) @description = mirrir_script.try(:description) @script = modify_shixun_script @shixun, script end def get_custom_script shixun_script = PlatformSample.where(:samples_type => "script").first.try(:contents) compile = params[:compile] execute = params[:executive] if shixun_script shixun_script = compile.blank? ? shixun_script.gsub("COMPILEFUNCTION", "").gsub("CHALLENGEFIELPATH", "") : shixun_script.gsub("COMPILEFUNCTION", "#{compile_command}").gsub("COMPILECOMMAND", "#{compile}") shixun_script = execute.blank? ? shixun_script.gsub("EXECUTEFUNCTION", "") : shixun_script.gsub("EXECUTECOMMAND", "#{execute}") shixun_script = modify_shixun_script @shixun, shixun_script end @shixun_script = shixun_script end def departments @scope = [] q = params[:q] if q && q.strip.present? @scope = School.where("name like ?", "%#{q.strip}%").pluck(:name) end end def get_mirror_script mirror = MirrorRepository.find(params[:mirror_id]) @script = mirror.mirror_scripts end # TODO: 目前实训只做软删除. def destroy # apply_records = ApplyAction.where(container_id: @shixun.id, container_type: "ApplyShixun") # apply_records.delete_all if apply_records # HomeworkCommonShixuns.where(shixun_id: @shixun).delete_all # @shixun.destroy @shixun.update_column(:status, -1) end # 开启挑战 # 以前在开启挑战的时候检测实训是否更新,更新则重置,觉得应该放在TPI更好 # 中间需要一个过渡动画 # TODO: 第一次开启实训都会去判断是否是纯选择题类型,感觉做成在创建关卡的时候就判断该实训是否是纯选择题更加合适 def shixun_exec if @shixun.opening_time.present? && @shixun.opening_time > Time.now && current_user.shixun_identity(@shixun) > User::EDU_SHIXUN_MEMBER tip_show_exception(-3, "#{@shixun.opening_time.strftime('%Y-%m-%d %H:%M:%S')}") end current_myshixun = @shixun.current_myshixun(current_user.id) if current_myshixun # 如果TPM和TPI的管卡数不相等或者关卡顺序错了,说明实训被极大的改动,需要重置 if current_myshixun.games.count != @shixun.challenges_count || current_myshixun.games.reorder(:challenge_id).pluck(:challenge_id) != @shixun.challenges.reorder(:id).pluck(:id) uid_logger_error("7777777777777777#{current_myshixun.games.count}, #{@shixun.challenges_count}") # 这里页面弹框要收到 当前用户myshixun的identifier. tip_show_exception("/myshixuns/#{current_myshixun.try(:identifier)}/reset_my_game") end # 如果存在实训,则直接进入实训 @current_task = current_myshixun.current_task else # 如果未创建关卡一定不能开启实训,否则TPI没法找到当前的关卡 if @shixun.challenges_count == 0 tip_exception("开启实战前请先创建实训关卡") end # 判断实训是否全为选择题 is_choice_type = @shixun.is_choice_type? if !is_choice_type commit = GitService.commits(repo_path: @repo_path).try(:first) Rails.logger.info("First comit########{commit}") tip_exception("开启实战前请先在版本库中提交代码") if commit.blank? commit_id = commit["id"] end ActiveRecord::Base.transaction do begin #cloud_bridge = edu_setting('cloud_bridge') myshixun_identifier = generate_identifier Myshixun, 10 myshixun = @shixun.myshixuns.create!(user_id: current_user.id, identifier: myshixun_identifier, modify_time: @shixun.modify_time, reset_time: @shixun.reset_time, onclick_time: Time.now, commit_id: commit_id) uid_logger("myshixun_id is #{myshixun.id}") # 如果实训是纯选择题,则不需要去fork仓库以及中间层的相关操作了 unless is_choice_type # fork仓库 project_fork(myshixun, @repo_path, current_user.login) #rep_url = Base64.urlsafe_encode64(repo_ip_url @repo_path ) #uid_logger("start openGameInstance") #uri = "#{cloud_bridge}/bridge/game/openGameInstance" #logger.info("end openGameInstance") #params = {tpiID: "#{myshixun.id}", tpmGitURL:rep_url, tpiRepoName: myshixun.repo_name.split("/").last} #uid_logger("openGameInstance params is #{params}") # res = interface_post uri, params, 83, "实训云平台繁忙(繁忙等级:83)" end # 其它创建关卡等操作 challenges = @shixun.challenges # 之所以增加user_id是为了方便统计查询性能 challenges.each_with_index do |challenge, index| status = (index == 0 ? 0 : 3) game_identifier = generate_identifier(Game, 12) Game.create!(:challenge_id => challenge.id, :myshixun_id => myshixun.id, :status => status, :user_id => myshixun.user_id, :open_time => Time.now, :identifier => game_identifier, :modify_time => challenge.modify_time) end # REDO:开启实训时更新关联作品的状态-> TODO:这个可以异步。 HomeworksService.new.update_myshixun_work_status myshixun @current_task = myshixun.current_task uid_logger("## shixun exec: myshixun id is #{myshixun.id}") rescue Exception => e uid_logger_error(e.message) tip_exception("实训云平台繁忙(繁忙等级:81)") raise ActiveRecord::Rollback end end end end # gameID 及实训ID # status: 0 , 1 申请过, 2,实训关卡路径未填, 3 实训标签未填, 4 实训未创建关卡 def publish @status = 0 @position = [] begin if @shixun.challenges.count == 0 @status = 4 else @shixun.challenges.each do |challenge| if challenge.challenge_tags.count == 0 @status = 3 @position << challenge.position end end unfinish_challenge = @shixun.challenges.where(:st => 0, :path => nil) if unfinish_challenge.count > 0 && !@shixun.is_choice_type? @status = 2 @pos = [] unfinish_challenge.each do |challenge| @pos << challenge.position end end end if @status == 0 @shixun.update_attributes!(:status => 1) apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first if apply && apply.status == 0 @status = 0 else ApplyAction.create(:container_type => "ApplyShixun", :container_id => @shixun.id, :user_id => current_user.id, :status => 0) #begin # status = Trustie::Sms.send(mobile: '18711011226', send_type:'publish_shixun' , name: '管理员') #rescue => e # Rails.logger.error "发送验证码出错: #{e}" #end @status = 1 end end rescue Exception => e logger.error("pushlish game #{e}") end end include GitCommon def update_file content = params[:content] author_name = current_user.full_name author_email = current_user.mail @content = update_file_content content, @repo_path, @path, author_email, author_name, "Edit by browser" end def add_collaborators raise("搜索内容不能为空") unless params[:search] member_ids = "(" + @shixun.shixun_members.map(&:user_id).join(',') + ")" condition = "%#{params[:search].strip}%".gsub(" ","") @users = User.where("id not in #{member_ids} and status = 1 and LOWER(concat(lastname, firstname, login, mail, nickname)) LIKE '#{condition}'") end def shixun_members_added raise("user_ids 不能为空!") if params[:user_ids].blank? memberships = params[:user_ids] memberships.each do |member| ShixunMember.create!(:user_id => member, :shixun_id => @shixun.id, :role => 2) end end def change_manager # 搜索成员 if request.get? @collaborators = @shixun.shixun_members.where("user_id != #{@shixun.user_id}") else if params[:user_id] man_member = ShixunMember.where(:shixun_id => @shixun.id, :user_id => @shixun.user_id).first cha_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id).first if man_member && cha_member man_member.update_attribute(:role, 2) cha_member.update_attribute(:role, 1) @shixun.update_attribute(:user_id, cha_member.user_id) end end end end # 刪除合作者 def collaborators_delete raise("user_id不能为空") if params[:user_id].blank? shixun_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id, :role => 2).first shixun_member.delete end # 实训的发送至课堂:搜索课堂 def search_user_courses ## 分页参数 page = params[:page] || 1 limit = params[:limit] || 20 if params[:search] search = "%#{params[:search].to_s.strip.downcase}%" course_ids = Course.find_by_sql("SELECT c.id FROM courses c, members m, member_roles mr WHERE m.course_id = c.id AND m.id=mr.member_id AND mr.role_id in (3,7,9) AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0 AND c.name like '#{search}' ").map(&:id) else course_ids = Course.find_by_sql("SELECT c.id, c.name FROM courses c, members m, member_roles mr WHERE m.course_id = c.id AND m.id=mr.member_id AND mr.role_id in (3,7,9) AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0").map(&:id) end @course_count = course_ids.length @courses = Course.where(:id => course_ids).page(page).per(limit) end # 将实训发送到课程 def send_to_course @course = Course.find(params[:course_id]) homework = HomeworksService.new.create_homework shixun, @course, nil, current_user end # 二维码扫描下载 def download_file file_path = params[:file_name] send_file "#{Rails.root}/#{file_path}", :filename => "#{file_path}", :type => 'shixun', :disposition => 'attachment' #inline can open in browser end # 撤销发布 def cancel_publish apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first if apply && apply.status == 0 apply.update_attribute(:status, 3) apply.tidings.destroy_all @shixun.update_column('status', 0) end end private def shixun_params raise("实训名称不能为空") if params[:name].blank? params.require(:shixun).permit(:name, :trainee, :webssh, :can_copy, :use_scope, :vnc, :test_set_permission, :task_pass, :repo_name, :multi_webssh, :opening_time, :mirror_script_id) end def find_shixun @shixun = Shixun.find_by_identifier(params[:identifier]) if @shixun.blank? normal_status(404, "...") return end if !current_user.shixun_permission(@shixun) tip_exception(403, "..") end end def find_repo_name @repo_path = @shixun.try(:repo_path) @path = params[:path] end def allowed unless current_user.manager_of_shixun?(@shixun) tip_exception(403, "..") end end def portion_allowed if current_user.shixun_identity(@shixun) > User::EDU_CERTIFICATION_TEACHER raise Educoder::TipException.new(403, "..") end end def special_allowed if @shixun.status != 2 tip_exception(403, "..") end end end