From 0b9a69ec56b4fd20e9b75862e8ba7b37100bd794 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 24 Jul 2020 15:35:43 +0800 Subject: [PATCH 01/47] ADD sha for get trsitie-pileline.yml file api --- README.md | 1 + app/controllers/dev_ops/builds_controller.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dc5546a3..1013d6f95 100644 --- a/README.md +++ b/README.md @@ -2429,6 +2429,7 @@ http://localhost:3000/api/dev_ops/builds/get_trustie_pipeline.json | jq { "name": ".trustie-pipeline.yml", "path": ".trustie-pipeline.yml", + "sha": "548sfefsafef48sf485s4f", "content": "..jsaf" } ``` diff --git a/app/controllers/dev_ops/builds_controller.rb b/app/controllers/dev_ops/builds_controller.rb index 2c419ea7d..8cbb48e35 100644 --- a/app/controllers/dev_ops/builds_controller.rb +++ b/app/controllers/dev_ops/builds_controller.rb @@ -47,7 +47,7 @@ class DevOps::BuildsController < ApplicationController file = interactor.result return render json: {} if file[:status] - json = {name: file['name'], path: file['path'], content: render_decode64_content(file['content'])} + json = {name: file['name'], path: file['path'], sha: file['sha'], content: render_decode64_content(file['content'])} render json: json end end From 69ea60dfd55d845bb8e16cd46bf2dfb006948ea1 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 6 Aug 2020 14:26:19 +0800 Subject: [PATCH 02/47] FIX update devops process --- README.md | 112 ++++++++++++++++++ app/controllers/application_controller.rb | 5 - app/controllers/concerns/devopsable.rb | 32 +++++ .../dev_ops/cloud_accounts_controller.rb | 32 +++-- .../dev_ops/languages_controller.rb | 1 + app/controllers/users_controller.rb | 19 ++- app/libs/dev_ops/drone/ci.rb | 2 +- app/libs/dev_ops/drone/server.rb | 23 +++- app/models/concerns/droneable.rb | 29 +++++ app/models/user.rb | 13 +- app/views/users/devops.json.jbuilder | 12 ++ app/views/users/get_user_info.json.jbuilder | 3 +- config/routes.rb | 11 +- ...20200731073851_add_devops_step_to_users.rb | 5 + 14 files changed, 276 insertions(+), 23 deletions(-) create mode 100644 app/controllers/concerns/devopsable.rb create mode 100644 app/models/concerns/droneable.rb create mode 100644 app/views/users/devops.json.jbuilder create mode 100644 db/migrate/20200731073851_add_devops_step_to_users.rb diff --git a/README.md b/README.md index 1013d6f95..8a6ada33d 100644 --- a/README.md +++ b/README.md @@ -2357,6 +2357,51 @@ http://localhost:3000/api//api/repositories/3868/delete_file | jq ### DevOps相关api --- +#### 获取devops流程步骤(判断devops是否初始化) +``` +GET /api/users/devops +``` + +*示例* +``` +curl -X GET \ +-d "project_id=5988" \ +https://localhost:3000/api/users/devops.json | jq +``` + +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|project_id |是|string |项目id或者项目的标识identifier| + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|step |int|初始化devops流程步骤; 0: 标识未开启devops,1: 标识用户已填写了云服务器相关信息,但并未开启认证, 2: 标识用户已开启了CI服务端的认证, 3: 标识用户已经授权并获取了CI服务的token| +|account |string|你的云服务器帐号| +|ip |string|你的云服务器帐号ip| +|secret |string|你的云服务器登录密码| +|authenticate_url |string|devops授权认证地址, 只有填写了服务器相关信息后才会有该地址| +|get_drone_token_url |string|获取CI服务端token地址, 只有认证成功后才会有该地址| + +返回值 +```json +{ + "step": 0, + "cloud_account": { + "id": 1, + "account": "xxx", + "ip": "xxx.xxx.xxx.x", + "secret": "11111", + "authenticate_url": "http://localhost:3000/login", + "get_drone_token_url": "http://localhost:3000/account" + } +} +``` +--- + #### 初始化DevOps流程 ``` POST /api/dev_ops/cloud_accounts @@ -2399,6 +2444,72 @@ https://localhost:3000/api/dev_ops/cloud_accounts.json | jq ``` --- +#### 用户认证CI服务端后,需要调用该接口进行更新devlops流程状态 +``` +PUT /api/users/devops_authenticate +``` +*示例* +``` +curl -X PUT \ +-d "project_id=5988" \ +http://localhost:3000/api/users/devops_authenticate.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|project_id |是|string |项目id或者项目的标识identifier| + + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|status |int|0:成功, -1: 失败| + +``` +{ + "status": 0, + "message": "success" +} +``` +--- + +#### 激活项目 +``` +POST /api/dev_ops/cloud_accounts/:id/activate +``` +*示例* +``` +curl -X POST \ +-d "id=1" \ +-d "project_id=4844" \ +-d "drone_token=xxxxxxxxxx" \ +http://localhost:3000/api/dev_ops/cloud_accounts/1/activate.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|project_id |是|int |project's id or identifier | +|id |是|int |cloud_account's id | +|drone_token |否|string |CI端用户的token值,只有当用户第一次激活时,才需要填写该值 | + + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|status |int|0:成功, -1: 失败| + +``` +{ + "status": 0, + "message": "success" +} +``` +--- + #### 获取仓库的.trustie-pipeline.yml ``` GET /api/dev_ops/builds/get_trustie_pipeline @@ -2433,6 +2544,7 @@ http://localhost:3000/api/dev_ops/builds/get_trustie_pipeline.json | jq "content": "..jsaf" } ``` +--- #### 获取语言列表 ``` diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e97c8ec08..9cb23ab2d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -744,11 +744,6 @@ class ApplicationController < ActionController::Base interactor.success? ? render_ok : render_error(interactor.error) end - # devops 权限验证 - def devops_authorize! - render_forbidden unless @project.owner?(current_user) - end - private def object_not_found uid_logger("Missing template or cant't find record, responding with 404") diff --git a/app/controllers/concerns/devopsable.rb b/app/controllers/concerns/devopsable.rb new file mode 100644 index 000000000..43828fd22 --- /dev/null +++ b/app/controllers/concerns/devopsable.rb @@ -0,0 +1,32 @@ +module Devopsable + extend ActiveSupport::Concern + + included do + end + + # devops 权限验证 + def devops_authorize! + render_forbidden unless @project.owner?(current_user) + end + + def auto_load_project + @project = Project.find_by(id: params[:project_id]) || Project.find_by(identifier: params[:project_id]) + render_not_found('未找到相关的项目') if @project.blank? + end + + # TODO 暂时限制项目拥有者才有权限操作 + def limit_owner_can_devops!(user) + return if @project.owner? user + render_forbidden + end + + def find_cloud_account + @cloud_account = DevOps::CloudAccount.find params[:id] + end + + def set_drone_token!(user, cloud_account, drone_token) + return if user.devops_has_token? + cloud_account.update_column(:drone_token, drone_token) + user.set_drone_step!(User::DEVOPS_HAS_TOKEN) + end +end diff --git a/app/controllers/dev_ops/cloud_accounts_controller.rb b/app/controllers/dev_ops/cloud_accounts_controller.rb index 10cd67bbd..d2e849eb5 100644 --- a/app/controllers/dev_ops/cloud_accounts_controller.rb +++ b/app/controllers/dev_ops/cloud_accounts_controller.rb @@ -1,7 +1,10 @@ class DevOps::CloudAccountsController < ApplicationController + include Devopsable + before_action :require_login - before_action :find_project + before_action :auto_load_project before_action :devops_authorize! + before_action :find_cloud_account, only: %i[activate] def create ActiveRecord::Base.transaction do @@ -14,8 +17,6 @@ class DevOps::CloudAccountsController < ApplicationController else cloud_account = DevOps::CloudAccount.new(create_params) cloud_account.user = current_user - cloud_account.repo_id = @project.repository.id - cloud_account.project_id = @project.id cloud_account.save! end @@ -50,6 +51,7 @@ class DevOps::CloudAccountsController < ApplicationController logger.info "######### redirect_url: #{redirect_url}" if result && !result.blank? + current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) render_ok(redirect_url: redirect_url) else render_error('激活失败, 请检查你的云服务器信息是否正确.') @@ -60,12 +62,28 @@ class DevOps::CloudAccountsController < ApplicationController render_error(ex.message) end + def activate + result = + if current_user.devops_has_token? + # 已有drone_token的,直接激活项目 + DevOps::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier).activate + else + # 没有token,说明是第一次激活devops, 需要用户填写token值 + return render_error('请先在CI服务端做用户认证.') if !current_user.devops_verified? + DevOps::Drone::API.new(params[:drone_token], @cloud_account.drone_url, @project.owner.login, @project.identifier).activate + end + + if result + set_drone_token!(current_user, @cloud_account, params[:drone_token]) + @project.update_column(:open_devops, true) + render_ok + else + render_error("激活失败,请检查你的token值是否正确.") + end + end + private def devops_params params.permit(:account, :secret, :ip_num, :project_id) end - - def find_project - @project = Project.find params[:project_id] - end end diff --git a/app/controllers/dev_ops/languages_controller.rb b/app/controllers/dev_ops/languages_controller.rb index 5863be647..be8b25b85 100644 --- a/app/controllers/dev_ops/languages_controller.rb +++ b/app/controllers/dev_ops/languages_controller.rb @@ -1,4 +1,5 @@ class DevOps::LanguagesController < ApplicationController + # TODO 需要开启权限认证,只有该项目devops初始化成功后才能获取语言列表 before_action :require_login before_action :find_langugae, only: :show diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ac60f4d18..00b1e37a5 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,8 +1,10 @@ class UsersController < ApplicationController + include Devopsable before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users] before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users] - before_action :require_login, only: %i[me list] + before_action :require_login, only: %i[me list devops_authenticate devops] + before_action :auto_load_project, only: %i[devops devops_authenticate] skip_before_action :check_sign, only: [:attachment_show] def list @@ -177,7 +179,7 @@ class UsersController < ApplicationController def trustie_projects user_id = User.select(:id, :login).where(login: params[:login])&.first&.id projects = Project.visible - + projects = projects.joins(:members).where(members: { user_id: user_id }) search = params[:search].to_s.strip @@ -212,6 +214,19 @@ class UsersController < ApplicationController render_ok end + def devops + @user = current_user + limit_owner_can_devops!(user) + @cloud_account = @user.dev_ops_cloud_account + end + + # devops 认证 + def devops_authenticate + limit_owner_can_devops!(current_user) + current_user.set_drone_step!(User::DEVOPS_VERIFIED) + render_ok + end + private def load_user @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id]) diff --git a/app/libs/dev_ops/drone/ci.rb b/app/libs/dev_ops/drone/ci.rb index cfa67cdc2..c2a06d4ce 100644 --- a/app/libs/dev_ops/drone/ci.rb +++ b/app/libs/dev_ops/drone/ci.rb @@ -20,6 +20,6 @@ class DevOps::Drone::Ci private def cmd - "cd ..; cd var/lib/drone/; sqlite3 database.sqlite; .dump; select user_hash from users where user_login=#{gitea_username} " + "cd ..; cd var/lib/drone/; sqlite3 database.sqlite; .dump; select user_hash from users where user_login=#{gitea_username};" end end diff --git a/app/libs/dev_ops/drone/server.rb b/app/libs/dev_ops/drone/server.rb index cb08ca271..152f27df4 100644 --- a/app/libs/dev_ops/drone/server.rb +++ b/app/libs/dev_ops/drone/server.rb @@ -18,7 +18,8 @@ class DevOps::Drone::Server def generate_cmd "service docker start; docker rm -f `docker ps -qa`; docker run \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v /var/lib/drone:/data \ + -e DRONE_DATABASE_DRIVER=mysql \ + -e DRONE_DATABASE_DATASOURCE=#{database_username}:#{database_password}@#{database_host}:3306/drone?parseTime=true \ -e DRONE_GITEA_SERVER=#{gitea_url} \ -e DRONE_GITEA_CLIENT_ID=#{client_id} \ -e DRONE_GITEA_CLIENT_SECRET=#{client_secret} \ @@ -37,4 +38,24 @@ class DevOps::Drone::Server def gitea_url Gitea.gitea_config[:domain] end + + def database_username + database_config[Rails.env]["username"] + end + + def database_password + database_config[Rails.env]["password"] + end + + def database_host + database_config[Rails.env]["host"] + end + + def database + database_config[Rails.env]["database"] + end + + def database_config + Rails.configuration.database_configuration + end end diff --git a/app/models/concerns/droneable.rb b/app/models/concerns/droneable.rb new file mode 100644 index 000000000..2e7206fb0 --- /dev/null +++ b/app/models/concerns/droneable.rb @@ -0,0 +1,29 @@ +module Droneable + extend ActiveSupport::Concern + + included do + end + + def devops_uninit? + self.devops_step === User::DEVOPS_UNINIT + end + + def devops_unverified? + self.devops_step === User::DEVOPS_UNVERIFIED + end + + def devops_verified? + self.devops_step === User::DEVOPS_VERIFIED + end + + def devops_has_token? + self.devops_step === User::DEVOPS_HAS_TOKEN + end + + def set_drone_step!(step) + self.update_column(:devops_step, step) + end + + module ClassMethods + end +end diff --git a/app/models/user.rb b/app/models/user.rb index f919ed1bf..9d6b24694 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,8 +5,16 @@ class User < ApplicationRecord include Likeable include BaseModel include ProjectOperable + include Droneable # include Searchable::Dependents::User + # devops step + # devops_step column: 0: 未填写服务器信息;1: 已填写服务器信息(未认证); 2: 已认证, 3: 已填写token值 + DEVOPS_UNINIT = 0 + DEVOPS_UNVERIFIED = 1 + DEVOPS_VERIFIED = 2 + DEVOPS_HAS_TOKEN = 3 + # Account statuses STATUS_ANONYMOUS = 0 STATUS_ACTIVE = 1 @@ -70,8 +78,9 @@ class User < ApplicationRecord # 关注 has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注 has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户 - - has_many :watchers, as: :watchable, dependent: :destroy + has_many :watchers, as: :watchable, dependent: :destroy + + has_one :dev_ops_cloud_account, class_name: 'DevOps::CloudAccount', dependent: :destroy # 认证 has_many :apply_user_authentication diff --git a/app/views/users/devops.json.jbuilder b/app/views/users/devops.json.jbuilder new file mode 100644 index 000000000..7ae5fa2c1 --- /dev/null +++ b/app/views/users/devops.json.jbuilder @@ -0,0 +1,12 @@ +json.step @user.devops_step +json.cloud_account do + if @cloud_account && !@user.devops_uninit? + json.account @cloud_account.account + json.ip @cloud_account.drone_ip + json.secret @cloud_account.visible_secret + json.authenticate_url "#{@cloud_account.drone_url}/login" if @user.devops_unverified? + json.get_drone_token_url "#{@cloud_account.drone_url}/account" if @user.devops_verified? + else + json.nil! + end +end diff --git a/app/views/users/get_user_info.json.jbuilder b/app/views/users/get_user_info.json.jbuilder index 1c83716bd..1c78d4fef 100644 --- a/app/views/users/get_user_info.json.jbuilder +++ b/app/views/users/get_user_info.json.jbuilder @@ -12,5 +12,4 @@ json.user_phone_binded @user.phone.present? # json.email @user.mail json.profile_completed @user.profile_completed? json.professional_certification @user.professional_certification - - +json.devops_step @user.devops_step diff --git a/config/routes.rb b/config/routes.rb index 8064352bf..e292a396c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,7 +16,11 @@ Rails.application.routes.draw do resources :edu_settings scope '/api' do namespace :dev_ops do - resources :cloud_accounts, only: [:create] + resources :cloud_accounts, only: [:create] do + member do + post :activate + end + end resources :languages, only: [:index, :show] do collection do get :common @@ -189,11 +193,12 @@ Rails.application.routes.draw do post :sync_salt get :trustie_projects get :trustie_related_projects + get :devops + put :devops_authenticate end scope module: :users do - # resources :courses, only: [:index] - resources :projects, only: [:index] + # resources :projects, only: [:index] # resources :subjects, only: [:index] resources :project_packages, only: [:index] # 私信 diff --git a/db/migrate/20200731073851_add_devops_step_to_users.rb b/db/migrate/20200731073851_add_devops_step_to_users.rb new file mode 100644 index 000000000..6815b797e --- /dev/null +++ b/db/migrate/20200731073851_add_devops_step_to_users.rb @@ -0,0 +1,5 @@ +class AddDevopsStepToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :devops_step, :integer, default: 0, comment: '0: uninit devops; 1: unverified; 2: verified' + end +end From 6bd809525815e7d4c4bf6e62b637742d56626c75 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Tue, 11 Aug 2020 23:20:04 +0800 Subject: [PATCH 03/47] =?UTF-8?q?FIX=20=E5=8D=87=E7=BA=A7=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E9=A1=B9=E7=9B=AE=E7=9B=B8=E5=85=B3=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 66 +++- app/controllers/application_controller.rb | 33 +- app/controllers/forks_controller.rb | 3 +- app/controllers/issue_tags_controller.rb | 13 +- app/controllers/issues_controller.rb | 11 +- app/controllers/members_controller.rb | 2 +- app/controllers/project_trends_controller.rb | 4 +- app/controllers/projects/base_controller.rb | 4 +- app/controllers/projects_controller.rb | 7 +- app/controllers/pull_requests_controller.rb | 11 +- app/controllers/repositories_controller.rb | 8 +- .../version_releases_controller.rb | 22 +- app/controllers/versions_controller.rb | 4 +- app/models/concerns/project_ability.rb | 14 + app/models/project.rb | 13 +- app/models/user.rb | 5 +- config/routes.rb | 310 ++++++++---------- 17 files changed, 284 insertions(+), 246 deletions(-) create mode 100644 app/models/concerns/project_ability.rb diff --git a/README.md b/README.md index 61f68b32a..8450de925 100644 --- a/README.md +++ b/README.md @@ -532,17 +532,18 @@ curl -X POST http://localhost:3000/api/repositories/1244/sync_mirror | jq #### 项目详情 ``` -GET api/projects/:id +GET /api/:namespace_id/:id ``` *示例* ``` -curl -X GET http://localhost:3000/api/projects/3263 | jq +curl -X GET http://localhost:3000/api/jasder/jasder_test | jq ``` *请求参数说明:* |参数名|必选|类型|说明| |-|-|-|-| -|id |是|int |项目id | +|namespace_id |是|string |用户登录名 | +|id |是|string |项目标识identifier | *返回参数说明:* @@ -571,6 +572,50 @@ curl -X GET http://localhost:3000/api/projects/3263 | jq ``` --- +#### 项目详情(简版) +``` +GET /api/:namespace_id/:id/simple +``` +*示例* +``` +curl -X GET http://localhost:3000/api/jasder/jasder_test/simple | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|id |是|int |项目id | + + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|id |int |id | +|name |string|项目名称| +|identifier |string|项目标识| +|is_public |boolean|项目是否公开, true:公开,false:私有| +|description |string|项目简介| +|repo_id |int|仓库id| +|repo_identifier|string|仓库标识| + + +返回值 +``` +{ + "identifier": "jasder_test", + "name": "jasder的测试项目", + "id": 4967, + "type": 0, + "author": { + "login": "jasder", + "name": "姓名", + "image_url": "avatars/User/b" + } +} +``` +--- + #### 编辑仓库信息 ``` GET /api/repositories/:id/edit.json @@ -898,13 +943,13 @@ curl -X POST http://localhost:3000/api/projects/3297/forks | jq #### 获取代码目录列表 ``` -POST /api/repositories/:id/entries.json +POST /api/:namespace_id/:project_id/repository/entries ``` *示例* ``` curl -X GET \ -d "ref=develop" \ -http://localhost:3000//api/repositories/3687/entries.json | jq +http://localhost:3000//api/jasder/jasder_test/repository/entries | jq ``` *请求参数说明:* @@ -1321,11 +1366,11 @@ http://localhost:3000/api/projects | jq ### 获取分支列表 ``` -GET /api/projects/:id/branches +GET /api/:namespace_id/:id/branches ``` *示例* ``` -curl -X GET http://localhost:3000/api/projects/4797/branches | jq +curl -X GET http://localhost:3000/api/jasder/jasder_test/branches | jq ``` *请求参数说明:* @@ -1488,18 +1533,19 @@ http://localhost:3000/api/repositories/5836/tags.json | jq ## 仓库详情 ``` -GET /api/repositories/:id +GET /api/:namespace_id/:project_id/repository ``` *示例* ``` curl -X GET \ -http://localhost:3000/api/repositories/23.json | jq +http://192.168.2.230:3000/api/jasder/forgeplus/repository | jq ``` *请求参数说明:* |参数名|必选|类型|说明| |-|-|-|-| -|id |是|string |项目id | +|namespace_id |是|string |用户登录名 | +|project_id |是|string |项目标识identifier | *返回参数说明:* diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 68f0b2a48..a4ae06504 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -384,7 +384,7 @@ class ApplicationController < ActionController::Base def current_user if Rails.env.development? - User.current = User.find 1 + User.current = User.find 36480 else User.current end @@ -727,11 +727,6 @@ class ApplicationController < ActionController::Base render_not_found("未找到’#{project}’相关的项目") unless @project end - def find_project_with_identifier - @project = Project.find_by_identifier! params[:id] - render_not_found("未找到’#{params[:id]}’相关的项目") unless @project - end - def find_project_with_id @project = Project.find(params[:project_id] || params[:id]) rescue Exception => e @@ -743,6 +738,32 @@ class ApplicationController < ActionController::Base interactor.success? ? render_ok : render_error(interactor.error) end + # projects + def load_project + namespace = params[:namespace_id] + id = params[:project_id] || params[:id] + + @project = Project.find_with_namespace(namespace, id) + + if @project and current_user.can_read_project?(@project) + logger.info "###########: has project and can read project" + @project + elsif current_user.is_a?(AnonymousUser) + logger.info "###########:This is AnonymousUser" + @project = nil if !@project.is_public? + render_forbidden and return + else + logger.info "###########:project not found" + @project = nil + render_not_found and return + end + @project + end + + def load_repository + @repository ||= load_project.repository + end + private def object_not_found uid_logger("Missing template or cant't find record, responding with 404") diff --git a/app/controllers/forks_controller.rb b/app/controllers/forks_controller.rb index 93772cd19..cb18091c1 100644 --- a/app/controllers/forks_controller.rb +++ b/app/controllers/forks_controller.rb @@ -1,5 +1,6 @@ class ForksController < ApplicationController - before_action :require_login, :find_project_with_id + before_action :require_login + before_action :load_project before_action :authenticate_project!, :authenticate_user! def create diff --git a/app/controllers/issue_tags_controller.rb b/app/controllers/issue_tags_controller.rb index dd4f2476c..fa12aefae 100644 --- a/app/controllers/issue_tags_controller.rb +++ b/app/controllers/issue_tags_controller.rb @@ -1,7 +1,7 @@ class IssueTagsController < ApplicationController before_action :require_login, except: [:index] - before_action :find_project_with_id - before_action :set_project + before_action :load_repository + before_action :set_user before_action :check_issue_permission, except: :index before_action :set_issue_tag, only: [:edit, :update, :destroy] @@ -121,13 +121,8 @@ class IssueTagsController < ApplicationController private - def set_project - # @project = Project.find_by_identifier! params[:project_id] - @repository = @project.repository + def set_user @user = @project.owner - normal_status(-1, "项目不存在") unless @project.present? - normal_status(-1, "仓库不存在") unless @repository.present? - normal_status(-1, "用户不存在") unless @user.present? end def check_issue_permission @@ -143,4 +138,4 @@ class IssueTagsController < ApplicationController end end -end \ No newline at end of file +end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 418ae4f81..f0578d10e 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -1,7 +1,7 @@ class IssuesController < ApplicationController before_action :require_login, except: [:index, :show, :index_chosen] - before_action :find_project_with_id - before_action :set_project_and_user + before_action :load_project + before_action :set_user before_action :check_issue_permission before_action :check_project_public, only: [:index ,:show, :copy, :index_chosen, :close_issue] @@ -15,7 +15,7 @@ class IssuesController < ApplicationController @user_admin_or_member = current_user.present? && current_user.logged? && (current_user.admin || @project.member?(current_user)) issues = @project.issues.issue_issue.issue_index_includes issues = issues.where(is_private: false) unless @user_admin_or_member - + @all_issues_size = issues.size @open_issues_size = issues.where.not(status_id: 5).size @close_issues_size = issues.where(status_id: 5).size @@ -347,11 +347,8 @@ class IssuesController < ApplicationController end private - def set_project_and_user - # @project = Project.find_by_identifier(params[:project_id]) || (Project.find params[:project_id]) || (Project.find params[:id]) + def set_user @user = @project&.owner - # normal_status(-1, "项目不存在") unless @project.present? - normal_status(-1, "用户不存在") unless @user.present? end def check_project_public diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 0a47f6a0d..2964eb1af 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -1,6 +1,6 @@ class MembersController < ApplicationController before_action :require_login - before_action :find_project_with_id + before_action :load_project before_action :find_user_with_id, only: %i[create remove change_role] before_action :operate!, except: %i[index] before_action :check_member_exists!, only: %i[create] diff --git a/app/controllers/project_trends_controller.rb b/app/controllers/project_trends_controller.rb index 476a571dc..dc1ffbdb4 100644 --- a/app/controllers/project_trends_controller.rb +++ b/app/controllers/project_trends_controller.rb @@ -1,5 +1,5 @@ class ProjectTrendsController < ApplicationController - before_action :find_project_with_id + before_action :load_repository before_action :check_project_public def index @@ -44,4 +44,4 @@ class ProjectTrendsController < ApplicationController normal_status(-1, "您没有权限") end end -end \ No newline at end of file +end diff --git a/app/controllers/projects/base_controller.rb b/app/controllers/projects/base_controller.rb index d874b4759..9811a2136 100644 --- a/app/controllers/projects/base_controller.rb +++ b/app/controllers/projects/base_controller.rb @@ -1,5 +1,7 @@ class Projects::BaseController < ApplicationController include PaginateHelper - before_action :require_login, :check_auth + before_action :load_project + before_action :load_repository + end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index eb6c79777..55e4bed97 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -2,8 +2,8 @@ class ProjectsController < ApplicationController include ApplicationHelper include OperateProjectAbilityAble include ProjectsHelper - before_action :require_login, except: %i[index branches group_type_list simple] - before_action :find_project_with_id, only: %i[show branches update destroy fork_users praise_users watch_users] + before_action :require_login, except: %i[index branches group_type_list simple, show] + before_action :load_project before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] @@ -100,8 +100,7 @@ class ProjectsController < ApplicationController end def simple - project = Project.includes(:owner, :repository).select(:id, :name, :identifier, :user_id, :project_type).find params[:id] - json_response(project) + json_response(@project) end private diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index bd4e0d8f8..15b03520c 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -1,7 +1,6 @@ class PullRequestsController < ApplicationController before_action :require_login, except: [:index, :show] - before_action :find_project_with_id - before_action :set_repository + before_action :load_repository before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos] # before_action :get_relatived, only: [:edit] include TagChosenHelper @@ -234,14 +233,6 @@ class PullRequestsController < ApplicationController private - - def set_repository - @repository = @project.repository - @user = @project.owner - normal_status(-1, "仓库不存在") unless @repository.present? - normal_status(-1, "用户不存在") unless @user.present? - end - def find_pull_request @pull_request = PullRequest.find_by_id(params[:id]) @issue = @pull_request&.issue diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index c8633eb92..a6c81a8de 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -1,11 +1,11 @@ class RepositoriesController < ApplicationController include ApplicationHelper include OperateProjectAbilityAble + before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror] - before_action :find_project_with_includes, only: :show - before_action :find_project, except: [:tags, :commit, :sync_mirror, :show] + before_action :load_project before_action :authorizate!, except: [:sync_mirror, :tags, :commit] - before_action :find_repository_by_id, only: %i[commit sync_mirror tags] + before_action :find_repository_by_id, only: %i[commit sync_mirror] before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror] before_action :get_ref, only: %i[entries sub_entries top_counts] before_action :get_latest_commit, only: %i[entries sub_entries top_counts] @@ -60,7 +60,7 @@ class RepositoriesController < ApplicationController end def tags - @tags = Gitea::Repository::Tags::ListService.new(current_user&.gitea_token, @repo.user.login, @repo.identifier, {page: params[:page], limit: params[:limit]}).call + @tags = Gitea::Repository::Tags::ListService.new(current_user&.gitea_token, @project.owner.login, @project.identifier, {page: params[:page], limit: params[:limit]}).call end def edit diff --git a/app/controllers/version_releases_controller.rb b/app/controllers/version_releases_controller.rb index 6717bf064..bc6f03037 100644 --- a/app/controllers/version_releases_controller.rb +++ b/app/controllers/version_releases_controller.rb @@ -1,6 +1,6 @@ class VersionReleasesController < ApplicationController - before_action :find_project_with_id - before_action :set_user_and_project + before_action :load_repository + before_action :set_user before_action :require_login, except: [:index] before_action :find_version , only: [:edit, :update, :destroy] @@ -78,14 +78,14 @@ class VersionReleasesController < ApplicationController ActiveRecord::Base.transaction do begin version_params = releases_params - + if @version.update_attributes!(version_params) create_attachments(params[:attachment_ids], @version) if params[:attachment_ids].present? git_version_release = Gitea::Versions::UpdateService.new(@user.gitea_token, @user.try(:login), @repository.try(:identifier), version_params, @version.try(:version_gid)).call unless git_version_release raise Error, "更新失败" end - + normal_status(0, "更新成功") else normal_status(-1, "更新失败") @@ -123,14 +123,8 @@ class VersionReleasesController < ApplicationController private - - def set_user_and_project - # @project = Project.find_by_id(params[:project_id]) - @repository = @project.repository #项目的仓库 - @user = @project.owner - unless @user.present? && @project.present? && @repository.present? - normal_status(-1, "仓库不存在") - end + def set_user + @user = @repository.user end def find_version @@ -140,7 +134,7 @@ class VersionReleasesController < ApplicationController end end - def releases_params + def releases_params { body: params[:body], draft: params[:draft] || false, @@ -151,7 +145,7 @@ class VersionReleasesController < ApplicationController } end - def create_attachments(attachment_ids, target) + def create_attachments(attachment_ids, target) attachment_ids.each do |id| attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) unless attachment.blank? diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 742d48313..5ec769e94 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -1,6 +1,6 @@ class VersionsController < ApplicationController before_action :require_login, except: [:index, :show] - before_action :find_project_with_id + before_action :load_repository before_action :check_issue_permission, except: [:show, :index] before_action :set_version, only: [:edit, :update, :destroy, :show,:update_status] @@ -166,4 +166,4 @@ class VersionsController < ApplicationController end end -end \ No newline at end of file +end diff --git a/app/models/concerns/project_ability.rb b/app/models/concerns/project_ability.rb new file mode 100644 index 000000000..682f6fdc0 --- /dev/null +++ b/app/models/concerns/project_ability.rb @@ -0,0 +1,14 @@ +module ProjectAbility + extend ActiveSupport::Concern + + included do + + end + + def can_read_project?(project) + return true if self.admin? + return false if !project.is_public? && !project.member?(self.id) + true + end + +end diff --git a/app/models/project.rb b/app/models/project.rb index f4fb1122f..bd2008af6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -160,7 +160,7 @@ class Project < ApplicationRecord member&.roles&.last&.name || permission end - def fork_project + def fork_project Project.find_by(id: self.forked_from_project_id) end @@ -168,4 +168,15 @@ class Project < ApplicationRecord joins(:members).where(members: { user_id: member_user_id}) end + def self.find_with_namespace(namespace_path, identifier) + logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} " + + user = User.find_by_login namespace_path + return nil if user.blank? + + project = user.projects.find_by(identifier: identifier) + + return nil if project.blank? + project + end end diff --git a/app/models/user.rb b/app/models/user.rb index f919ed1bf..9518f59c6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,6 +5,7 @@ class User < ApplicationRecord include Likeable include BaseModel include ProjectOperable + include ProjectAbility # include Searchable::Dependents::User # Account statuses @@ -70,8 +71,8 @@ class User < ApplicationRecord # 关注 has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注 has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户 - - has_many :watchers, as: :watchable, dependent: :destroy + + has_many :watchers, as: :watchable, dependent: :destroy # 认证 has_many :apply_user_authentication diff --git a/config/routes.rb b/config/routes.rb index 45ba48550..8f61f0477 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,8 +16,8 @@ Rails.application.routes.draw do resources :edu_settings scope '/api' do - resources :sync_forge, only: [:create] do - collection do + resources :sync_forge, only: [:create] do + collection do post :sync_users end end @@ -67,41 +67,6 @@ Rails.application.routes.draw do end end resources :projects do - resources :hooks - resources :pull_requests, except: [:destroy] do - member do - post :pr_merge - # post :check_merge - post :refuse_merge - end - collection do - post :check_can_merge - get :create_merge_infos - get :get_branches - end - end - resources :version_releases, only: [:index,:new, :create, :edit, :update, :destroy] - resources :project_trends, only: [:index, :create] - resources :issues do - collection do - get :commit_issues - get :index_chosen - post :clean - post :series_update - end - member do - post :copy - post :close_issue - post :lock_issue - end - end - resources :issue_tags, only: [:create, :edit, :update, :destroy, :index] - resources :versions do - member do - post :update_status - end - end - resources :praise_tread, only: [:index] do collection do post :like @@ -109,28 +74,147 @@ Rails.application.routes.draw do get :check_like end end - resources :members, only: [:index, :create] do - collection do - delete :remove - put :change_role - end - end - resources :forks, only: [:create] + collection do post :migrate get :group_type_list - post :watch - end - member do - get :branches - post :watch - get :watch_users - get :praise_users - get :fork_users - get :simple end end + # Project Area START + resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? :watchers + get :praise_users, :path => :stargazers + get :fork_users, :path => :members + end + + resource :repository, only: [:show, :create, :edit] do + member do + get 'archive' + get 'top_counts' + get 'entries' + get 'sub_entries' + get 'commits' + get 'tags' + end + end + + resources :issues do + collection do + get :commit_issues + get :index_chosen + post :clean + post :series_update + end + member do + post :copy + post :close_issue + post :lock_issue + end + end + + resources :pull_requests, :path => :pulls, except: [:destroy] do + member do + post :pr_merge + # post :check_merge + post :refuse_merge + end + collection do + post :check_can_merge + get :create_merge_infos + get :get_branches + end + end + + resources :versions, :path => :milestones do + member do + post :update_status + end + end + + resources :members, :path => :collaborators, only: [:index, :create] do + collection do + delete :remove + put :change_role + end + end + + resources :hooks + resources :forks, only: [:create] + resources :project_trends, :path => :activity, only: [:index, :create] + resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] + resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] + + scope module: :projects do + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + end + end + end + # Project Area END + resources :accounts do collection do post :login @@ -176,7 +260,7 @@ Rails.application.routes.draw do scope module: :users do # resources :courses, only: [:index] - resources :projects, only: [:index] + # resources :projects, only: [:index] # resources :subjects, only: [:index] resources :project_packages, only: [:index] # 私信 @@ -186,11 +270,6 @@ Rails.application.routes.draw do # resource :unread_message_info, only: [:show] end - - resources :projects, module: :users, only: [] do - get :search, on: :collection - end - resources :tidings, only: [:index] scope module: :users do @@ -245,119 +324,6 @@ Rails.application.routes.draw do end end - resources :courses do - member do - get 'settings', :action => 'settings', :as => 'settings' - post 'set_invite_code_halt' - post 'set_public_or_private' - post 'search_teacher_candidate' - post 'add_teacher' - post 'create_graduation_group' - post 'join_graduation_group' - post 'set_course_group' - post 'change_course_admin' - post 'change_member_role' - post 'change_course_teacher' - post 'delete_course_teacher' - post 'teacher_application_review' - post 'transfer_to_course_group' - post 'delete_from_course' - post 'add_students_by_search' - post 'create_group_by_importing_file' - post 'duplicate_course' - post 'visits_plus_one' - get 'get_historical_courses' - get 'get_historical_course_students' - get 'course_group_list' - get 'add_teacher_popup' - get 'teachers' - get 'apply_teachers' - get 'graduation_group_list' - get 'top_banner' - get 'left_banner' - get 'students' - get 'all_course_groups' - get 'search_users' - get 'base_info' - get 'attahcment_category_list' - get 'export_member_scores_excel' #导出课堂信息 - get 'export_couser_info' - get 'export_member_act_score' - post 'switch_to_teacher' - post 'switch_to_assistant' - post 'switch_to_student' - post 'exit_course' - get 'informs' - post 'update_informs' - post 'new_informs' - delete 'delete_informs' - get 'online_learning' - post 'join_excellent_course' - get 'tasks_list' - post 'update_task_position' - get 'course_groups' - post 'join_course_group' - get 'work_score' - get 'act_score' - get 'statistics' - get 'course_videos' - delete 'delete_course_video' - post :inform_up - post :inform_down - end - - collection do - post 'apply_to_join_course' - post 'search_course_list' - get 'board_list' - get 'mine' - get 'search_slim' - end - - resources :course_stages, shallow: true do - member do - post :up_position - post :down_position - end - end - - resources :course_groups, shallow: true do - member do - post 'rename_group' - post 'move_category' - post 'set_invite_code_halt' - end - end - end - - - resources :course_modules, shallow: true do - member do - get 'sticky_module' - get 'hidden_module' - post 'rename_module' - post 'add_second_category' - end - collection do - post 'unhidden_modules' - end - end - - resources :course_second_categories, shallow: true do - member do - post 'rename_category' - post 'move_category' - end - end - - - resources :repertoires, only: [:index] - - scope module: :projects do - resources :project_applies, only: [:create] - end - - namespace :wechats do resource :js_sdk_signature, only: [:create] end From 5f36b517b4ebb0e705acdc14e3901cd14a228fd8 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 10:32:51 +0800 Subject: [PATCH 04/47] =?UTF-8?q?FIX=20=E5=AE=8C=E5=96=84=E8=B7=AF?= =?UTF-8?q?=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.rb | 266 ++++++++++++++++++++++++++--------------------- 1 file changed, 145 insertions(+), 121 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 8f61f0477..6a1f91174 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -82,134 +82,158 @@ Rails.application.routes.draw do end # Project Area START - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? :watchers - get :praise_users, :path => :stargazers - get :fork_users, :path => :members + get 'archive' + get 'top_counts' + get 'entries' + get 'sub_entries' + get 'commits' + get 'tags' + end + end + + resources :issues do + collection do + get :commit_issues + get :index_chosen + post :clean + post :series_update + end + member do + post :copy + post :close_issue + post :lock_issue + end + end + + resources :pull_requests, :path => :pulls, except: [:destroy] do + member do + post :pr_merge + # post :check_merge + post :refuse_merge + end + collection do + post :check_can_merge + get :create_merge_infos + get :get_branches + end + end + + resources :versions, :path => :milestones do + member do + post :update_status + end + end + + resources :members, :path => :collaborators, only: [:index, :create] do + collection do + delete :remove + put :change_role + end + end + + resources :hooks + resources :forks, only: [:create] + resources :project_trends, :path => :activity, only: [:index, :create] + resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] + resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] + + scope module: :projects do + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) end - resource :repository, only: [:show, :create, :edit] do - member do - get 'archive' - get 'top_counts' - get 'entries' - get 'sub_entries' - get 'commits' - get 'tags' - end + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) end - resources :issues do - collection do - get :commit_issues - get :index_chosen - post :clean - post :series_update - end - member do - post :copy - post :close_issue - post :lock_issue - end + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) end - resources :pull_requests, :path => :pulls, except: [:destroy] do - member do - post :pr_merge - # post :check_merge - post :refuse_merge - end - collection do - post :check_can_merge - get :create_merge_infos - get :get_branches - end - end - - resources :versions, :path => :milestones do - member do - post :update_status - end - end - - resources :members, :path => :collaborators, only: [:index, :create] do - collection do - delete :remove - put :change_role - end - end - - resources :hooks - resources :forks, only: [:create] - resources :project_trends, :path => :activity, only: [:index, :create] - resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] - resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] - - scope module: :projects do - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) end end end From 009b1976a79685996c915904abd972bef437d857 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 10:35:19 +0800 Subject: [PATCH 05/47] FIX bug --- app/controllers/application_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a4ae06504..159832177 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -740,8 +740,8 @@ class ApplicationController < ActionController::Base # projects def load_project - namespace = params[:namespace_id] - id = params[:project_id] || params[:id] + namespace = params[:owner] + id = params[:repo] || params[:id] @project = Project.find_with_namespace(namespace, id) From 54b1af8d06c2d0693617284fce04cb72e3e173b6 Mon Sep 17 00:00:00 2001 From: "sylor_huang@126.com" Date: Wed, 12 Aug 2020 10:55:13 +0800 Subject: [PATCH 06/47] Change --- app/controllers/application_controller.rb | 2 +- app/controllers/projects_controller.rb | 2 +- app/models/project.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 159832177..0ea9db177 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -750,7 +750,7 @@ class ApplicationController < ActionController::Base @project elsif current_user.is_a?(AnonymousUser) logger.info "###########:This is AnonymousUser" - @project = nil if !@project.is_public? + @project = nil if !@project.is_public render_forbidden and return else logger.info "###########:project not found" diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 93c5f6941..30fa96601 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -116,7 +116,7 @@ class ProjectsController < ApplicationController end def project_public? - return if @project.is_public? + return if @project.is_public if current_user return if current_user.admin? || @project.member?(current_user.id) diff --git a/app/models/project.rb b/app/models/project.rb index bd2008af6..2519700ba 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -122,7 +122,7 @@ class Project < ApplicationRecord def can_visited? - is_public? || User.current.admin? || member?(User.current) + is_public || User.current.admin? || member?(User.current) end def releases_size(current_user_id, type) From b46f9667ca15632375aa0ad4111eaace2251e5fe Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 10:56:28 +0800 Subject: [PATCH 07/47] =?UTF-8?q?FIX=20=E5=A4=84=E7=90=86=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=BA=93=E7=9B=B8=E5=85=B3routes=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E8=B7=AF=E7=94=B1=E6=97=A0=E6=95=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.rb | 317 ++++++++++++++++++++++++----------------------- 1 file changed, 159 insertions(+), 158 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 6a1f91174..b1910c93a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,164 +81,6 @@ Rails.application.routes.draw do end end - # Project Area START - scope "/:owner/:repo" do - scope do - get( - '/activity', - to: 'project_trends#index', - as: :project_activity - ) - - get( - '/branches', - to: 'projects#branches', - as: :project_branches - ) - - get( - '/simple', - to: 'projects#simple', - as: :project_simple - ) - - get( - '/watchers', - to: 'projects#watch_users', - as: :project_watchers - ) - - get( - '/stargazers', - to: 'projects#praise_users', - as: :project_stargazers - ) - - get( - '/members', - to: 'projects#fork_users', - as: :project_members - ) - end - - resource :repositories, path: '/', only: [:show, :create, :edit] do - member do - get 'archive' - get 'top_counts' - get 'entries' - get 'sub_entries' - get 'commits' - get 'tags' - end - end - - resources :issues do - collection do - get :commit_issues - get :index_chosen - post :clean - post :series_update - end - member do - post :copy - post :close_issue - post :lock_issue - end - end - - resources :pull_requests, :path => :pulls, except: [:destroy] do - member do - post :pr_merge - # post :check_merge - post :refuse_merge - end - collection do - post :check_can_merge - get :create_merge_infos - get :get_branches - end - end - - resources :versions, :path => :milestones do - member do - post :update_status - end - end - - resources :members, :path => :collaborators, only: [:index, :create] do - collection do - delete :remove - put :change_role - end - end - - resources :hooks - resources :forks, only: [:create] - resources :project_trends, :path => :activity, only: [:index, :create] - resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] - resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] - - scope module: :projects do - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - end - end - # Project Area END - resources :accounts do collection do post :login @@ -675,4 +517,163 @@ Rails.application.routes.draw do ## react用 get '*path', to: 'main#index', constraints: ReactConstraint.new + + + # Project Area START + scope "/:owner/:repo" do + scope do + get( + '/activity', + to: 'project_trends#index', + as: :project_activity + ) + + get( + '/branches', + to: 'projects#branches', + as: :project_branches + ) + + get( + '/simple', + to: 'projects#simple', + as: :project_simple + ) + + get( + '/watchers', + to: 'projects#watch_users', + as: :project_watchers + ) + + get( + '/stargazers', + to: 'projects#praise_users', + as: :project_stargazers + ) + + get( + '/members', + to: 'projects#fork_users', + as: :project_members + ) + end + + resource :repositories, path: '/', only: [:show, :create, :edit] do + member do + get 'archive' + get 'top_counts' + get 'entries' + get 'sub_entries' + get 'commits' + get 'tags' + end + end + + resources :issues do + collection do + get :commit_issues + get :index_chosen + post :clean + post :series_update + end + member do + post :copy + post :close_issue + post :lock_issue + end + end + + resources :pull_requests, :path => :pulls, except: [:destroy] do + member do + post :pr_merge + # post :check_merge + post :refuse_merge + end + collection do + post :check_can_merge + get :create_merge_infos + get :get_branches + end + end + + resources :versions, :path => :milestones do + member do + post :update_status + end + end + + resources :members, :path => :collaborators, only: [:index, :create] do + collection do + delete :remove + put :change_role + end + end + + resources :hooks + resources :forks, only: [:create] + resources :project_trends, :path => :activity, only: [:index, :create] + resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] + resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] + + scope module: :projects do + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + end + end + # Project Area END end From d80600a58ce553c714d3342beaf4609dbc2574f8 Mon Sep 17 00:00:00 2001 From: "sylor_huang@126.com" Date: Wed, 12 Aug 2020 10:58:16 +0800 Subject: [PATCH 08/47] Change --- app/controllers/application_controller.rb | 2 +- app/controllers/projects_controller.rb | 2 +- app/models/project.rb | 2 +- app/views/users/projects/shared/_project.json.jbuilder | 4 ++-- app/views/users/subjects/shared/_subject.json.jbuilder | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0ea9db177..d5f879696 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -750,7 +750,7 @@ class ApplicationController < ActionController::Base @project elsif current_user.is_a?(AnonymousUser) logger.info "###########:This is AnonymousUser" - @project = nil if !@project.is_public + @project = nil if @project && !@project.is_public? render_forbidden and return else logger.info "###########:project not found" diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 30fa96601..93c5f6941 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -116,7 +116,7 @@ class ProjectsController < ApplicationController end def project_public? - return if @project.is_public + return if @project.is_public? if current_user return if current_user.admin? || @project.member?(current_user.id) diff --git a/app/models/project.rb b/app/models/project.rb index 2519700ba..bd2008af6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -122,7 +122,7 @@ class Project < ApplicationRecord def can_visited? - is_public || User.current.admin? || member?(User.current) + is_public? || User.current.admin? || member?(User.current) end def releases_size(current_user_id, type) diff --git a/app/views/users/projects/shared/_project.json.jbuilder b/app/views/users/projects/shared/_project.json.jbuilder index 9d1bad441..5cc985909 100644 --- a/app/views/users/projects/shared/_project.json.jbuilder +++ b/app/views/users/projects/shared/_project.json.jbuilder @@ -5,8 +5,8 @@ json.members_count project.members.count json.issues_count project.issues.count json.changesets_count project.project_score&.changeset_num.to_i -json.is_public project&.is_public -json.can_visited project&.can_visited? +json.is_public project.is_public? +json.can_visited project.can_visited? json.owner do json.partial! 'users/shared/real_user', user: project.owner diff --git a/app/views/users/subjects/shared/_subject.json.jbuilder b/app/views/users/subjects/shared/_subject.json.jbuilder index ad83b6ece..2059bc461 100644 --- a/app/views/users/subjects/shared/_subject.json.jbuilder +++ b/app/views/users/subjects/shared/_subject.json.jbuilder @@ -5,4 +5,4 @@ json.image_url url_to_avatar(subject) json.owner_id subject.user.id json.owner_name subject.user.full_name json.visits_count subject.visits -json.can_visited subject&.can_visited? +json.can_visited subject.can_visited? From facf7a0040296810fdc8d107df27e7f266392958 Mon Sep 17 00:00:00 2001 From: "sylor_huang@126.com" Date: Wed, 12 Aug 2020 11:00:39 +0800 Subject: [PATCH 09/47] Chnage --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d5f879696..f78089558 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -750,7 +750,7 @@ class ApplicationController < ActionController::Base @project elsif current_user.is_a?(AnonymousUser) logger.info "###########:This is AnonymousUser" - @project = nil if @project && !@project.is_public? + @project = nil if !@project&.is_public? render_forbidden and return else logger.info "###########:project not found" From ff847184ccd2e7271598329feedcb419c8641d47 Mon Sep 17 00:00:00 2001 From: "sylor_huang@126.com" Date: Wed, 12 Aug 2020 11:01:34 +0800 Subject: [PATCH 10/47] Change --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f78089558..159832177 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -750,7 +750,7 @@ class ApplicationController < ActionController::Base @project elsif current_user.is_a?(AnonymousUser) logger.info "###########:This is AnonymousUser" - @project = nil if !@project&.is_public? + @project = nil if !@project.is_public? render_forbidden and return else logger.info "###########:project not found" From 6525608f0b1ec74098c35fec6065cfe00c54de93 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 11:04:25 +0800 Subject: [PATCH 11/47] FIX routes namepace --- config/routes.rb | 315 ++++++++++++++++++++++++----------------------- 1 file changed, 158 insertions(+), 157 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index d09036bb0..42b053497 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -243,6 +243,164 @@ Rails.application.routes.draw do end end end + + # Project Area START + scope "/:owner/:repo" do + scope do + get( + '/activity', + to: 'project_trends#index', + as: :project_activity + ) + + get( + '/branches', + to: 'projects#branches', + as: :project_branches + ) + + get( + '/simple', + to: 'projects#simple', + as: :project_simple + ) + + get( + '/watchers', + to: 'projects#watch_users', + as: :project_watchers + ) + + get( + '/stargazers', + to: 'projects#praise_users', + as: :project_stargazers + ) + + get( + '/members', + to: 'projects#fork_users', + as: :project_members + ) + end + + resource :repositories, path: '/', only: [:show, :create, :edit] do + member do + get 'archive' + get 'top_counts' + get 'entries' + get 'sub_entries' + get 'commits' + get 'tags' + end + end + + resources :issues do + collection do + get :commit_issues + get :index_chosen + post :clean + post :series_update + end + member do + post :copy + post :close_issue + post :lock_issue + end + end + + resources :pull_requests, :path => :pulls, except: [:destroy] do + member do + post :pr_merge + # post :check_merge + post :refuse_merge + end + collection do + post :check_can_merge + get :create_merge_infos + get :get_branches + end + end + + resources :versions, :path => :milestones do + member do + post :update_status + end + end + + resources :members, :path => :collaborators, only: [:index, :create] do + collection do + delete :remove + put :change_role + end + end + + resources :hooks + resources :forks, only: [:create] + resources :project_trends, :path => :activity, only: [:index, :create] + resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] + resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] + + scope module: :projects do + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + end + end + # Project Area END end namespace :admins do @@ -520,161 +678,4 @@ Rails.application.routes.draw do get '*path', to: 'main#index', constraints: ReactConstraint.new - # Project Area START - scope "/:owner/:repo" do - scope do - get( - '/activity', - to: 'project_trends#index', - as: :project_activity - ) - - get( - '/branches', - to: 'projects#branches', - as: :project_branches - ) - - get( - '/simple', - to: 'projects#simple', - as: :project_simple - ) - - get( - '/watchers', - to: 'projects#watch_users', - as: :project_watchers - ) - - get( - '/stargazers', - to: 'projects#praise_users', - as: :project_stargazers - ) - - get( - '/members', - to: 'projects#fork_users', - as: :project_members - ) - end - - resource :repositories, path: '/', only: [:show, :create, :edit] do - member do - get 'archive' - get 'top_counts' - get 'entries' - get 'sub_entries' - get 'commits' - get 'tags' - end - end - - resources :issues do - collection do - get :commit_issues - get :index_chosen - post :clean - post :series_update - end - member do - post :copy - post :close_issue - post :lock_issue - end - end - - resources :pull_requests, :path => :pulls, except: [:destroy] do - member do - post :pr_merge - # post :check_merge - post :refuse_merge - end - collection do - post :check_can_merge - get :create_merge_infos - get :get_branches - end - end - - resources :versions, :path => :milestones do - member do - post :update_status - end - end - - resources :members, :path => :collaborators, only: [:index, :create] do - collection do - delete :remove - put :change_role - end - end - - resources :hooks - resources :forks, only: [:create] - resources :project_trends, :path => :activity, only: [:index, :create] - resources :issue_tags, :path => :labels, only: [:create, :edit, :update, :destroy, :index] - resources :version_releases, :path => :releases, only: [:index,:new, :create, :edit, :update, :destroy] - - scope module: :projects do - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - end - end - # Project Area END end From e2926acd2f495aa34b96b430f7aa989c81353363 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 11:14:57 +0800 Subject: [PATCH 12/47] FIX some routes bug --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 93c5f6941..40f3f910e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -3,7 +3,7 @@ class ProjectsController < ApplicationController include OperateProjectAbilityAble include ProjectsHelper before_action :require_login, except: %i[index branches group_type_list simple, show] - before_action :load_project + before_action :load_project, except: %i[group_type_list migrate] before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] From 066dede6e3b11a12f0c1e5bc688724536409a2d6 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 11:18:14 +0800 Subject: [PATCH 13/47] FIX bug --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 40f3f910e..739e9ec68 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -3,7 +3,7 @@ class ProjectsController < ApplicationController include OperateProjectAbilityAble include ProjectsHelper before_action :require_login, except: %i[index branches group_type_list simple, show] - before_action :load_project, except: %i[group_type_list migrate] + before_action :load_project, except: %i[index group_type_list migrate] before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] From 082c3b05b37d4283e6f2b797ab84d2b006b703a7 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 14:51:12 +0800 Subject: [PATCH 14/47] FIX repositories controller bug --- .../concerns/operate_project_ability_able.rb | 2 +- app/controllers/repositories_controller.rb | 11 +++--- config/routes.rb | 36 +++++++------------ 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/app/controllers/concerns/operate_project_ability_able.rb b/app/controllers/concerns/operate_project_ability_able.rb index 13f48e6cb..4d18ae1e4 100644 --- a/app/controllers/concerns/operate_project_ability_able.rb +++ b/app/controllers/concerns/operate_project_ability_able.rb @@ -10,7 +10,7 @@ module OperateProjectAbilityAble end def authorizate_user_can_edit_repo! - return if @repo.project.manager?(current_user) || current_user.admin? + return if @repository.project.manager?(current_user) || current_user.admin? render_forbidden('你没有权限操作.') end diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index a6c81a8de..c8ab51cba 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -3,9 +3,8 @@ class RepositoriesController < ApplicationController include OperateProjectAbilityAble before_action :require_login, only: %i[edit update create_file update_file delete_file sync_mirror] - before_action :load_project + before_action :load_repository before_action :authorizate!, except: [:sync_mirror, :tags, :commit] - before_action :find_repository_by_id, only: %i[commit sync_mirror] before_action :authorizate_user_can_edit_repo!, only: %i[sync_mirror] before_action :get_ref, only: %i[entries sub_entries top_counts] before_action :get_latest_commit, only: %i[entries sub_entries top_counts] @@ -56,7 +55,7 @@ class RepositoriesController < ApplicationController end def commit - @commit = Gitea::Repository::Commits::GetService.new(@repo.user.login, @repo.identifier, params[:sha], current_user.gitea_token).call + @commit = Gitea::Repository::Commits::GetService.new(@repository.user.login, @repository.identifier, params[:sha], current_user.gitea_token).call end def tags @@ -102,10 +101,10 @@ class RepositoriesController < ApplicationController end def sync_mirror - return render_error("正在镜像中..") if @repo.mirror.waiting? + return render_error("正在镜像中..") if @repository.mirror.waiting? - @repo.sync_mirror! - SyncMirroredRepositoryJob.perform_later(@repo.id, current_user.id) + @repository.sync_mirror! + SyncMirroredRepositoryJob.perform_later(@repository.id, current_user.id) render_ok end diff --git a/config/routes.rb b/config/routes.rb index 42b053497..fb7d8be63 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -155,23 +155,6 @@ Rails.application.routes.draw do end end - resources :repositories, only: [:index, :show, :edit] do - member do - get :entries - match :sub_entries, :via => [:get, :put] - get :commits - post :files - get :tags - post :create_file - put :update_file - delete :delete_file - post :repo_hook - post :sync_mirror - get :top_counts - get 'commits/:sha', to: 'repositories#commit', as: 'commit' - end - end - resources :users_for_private_messages, only: [:index] resources :files, only: [:index, :show, :update] do @@ -286,12 +269,19 @@ Rails.application.routes.draw do resource :repositories, path: '/', only: [:show, :create, :edit] do member do - get 'archive' - get 'top_counts' - get 'entries' - get 'sub_entries' - get 'commits' - get 'tags' + get :archive + get :top_counts + get :entries + match :sub_entries, :via => [:get, :put] + get :commits + get :tags + post :create_file + put :update_file + delete :delete_file + post :repo_hook + post :sync_mirror + get :top_counts + get 'commits/:sha', to: 'repositories#commit', as: 'commit' end end From 0e951df5d0104f18773578973c7a688d269e9f6d Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 15:03:23 +0800 Subject: [PATCH 15/47] FIX pull_requests new action bug --- app/controllers/pull_requests_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index 15b03520c..ad145f492 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -1,6 +1,7 @@ class PullRequestsController < ApplicationController before_action :require_login, except: [:index, :show] before_action :load_repository + before_action :set_user, only: [:new, :get_branches] before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos] # before_action :get_relatived, only: [:edit] include TagChosenHelper @@ -233,6 +234,10 @@ class PullRequestsController < ApplicationController private + def set_user + @user = @project.owner + end + def find_pull_request @pull_request = PullRequest.find_by_id(params[:id]) @issue = @pull_request&.issue From a0789cbd2253d285b74ac1fdce429e8e0af4fbfa Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 18:22:03 +0800 Subject: [PATCH 16/47] FIX project routes bug --- config/routes.rb | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index fb7d8be63..a46d8d880 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -235,36 +235,16 @@ Rails.application.routes.draw do to: 'project_trends#index', as: :project_activity ) + end - get( - '/branches', - to: 'projects#branches', - as: :project_branches - ) - - get( - '/simple', - to: 'projects#simple', - as: :project_simple - ) - - get( - '/watchers', - to: 'projects#watch_users', - as: :project_watchers - ) - - get( - '/stargazers', - to: 'projects#praise_users', - as: :project_stargazers - ) - - get( - '/members', - to: 'projects#fork_users', - as: :project_members - ) + resource :projects, path: '/' do + member do + get :branches + get :simple + get :watchers, to: 'projects#watch_users' + get :stargazers, to: 'projects#praise_users' + get :members, to: 'projects#fork_users' + end end resource :repositories, path: '/', only: [:show, :create, :edit] do From 0a9fbfd101800e2dd0b6ef50214ab644c17fdc8f Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 18:40:45 +0800 Subject: [PATCH 17/47] FIX entries api bug --- app/views/repositories/_commit.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/repositories/_commit.json.jbuilder b/app/views/repositories/_commit.json.jbuilder index 5ad8a1bce..888508839 100644 --- a/app/views/repositories/_commit.json.jbuilder +++ b/app/views/repositories/_commit.json.jbuilder @@ -1,6 +1,6 @@ json.commit do json.sha commit['sha'] - json.url EduSetting.get('host_name') + commit_repository_path(project.repository, commit['sha']) + # json.url EduSetting.get('host_name') + commit_repository_path(project.repository, commit['sha']) json.message commit['commit']['message'] json.author commit['commit']['author'] json.committer commit['commit']['committer'] From 193ba325ce2b3260da57de8c9b4bd526ac9fd156 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 22:24:06 +0800 Subject: [PATCH 18/47] FIX code bug --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 739e9ec68..97753a5f5 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -2,7 +2,7 @@ class ProjectsController < ApplicationController include ApplicationHelper include OperateProjectAbilityAble include ProjectsHelper - before_action :require_login, except: %i[index branches group_type_list simple, show] + before_action :require_login, except: %i[index branches group_type_list simple show] before_action :load_project, except: %i[index group_type_list migrate] before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] From 3becb4eabc07720618055f9485194d83c154d701 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Wed, 12 Aug 2020 22:41:48 +0800 Subject: [PATCH 19/47] FIX projects routes for show action bug --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index a46d8d880..7012142cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -237,7 +237,7 @@ Rails.application.routes.draw do ) end - resource :projects, path: '/' do + resource :projects, path: '/', except: [:show] do member do get :branches get :simple From 72ba784e70eaf8a5f11c54d40e4c8764210a351d Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 11:06:34 +0800 Subject: [PATCH 20/47] =?UTF-8?q?FIX=20=E7=89=88=E6=9C=AC=E5=BA=93?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=80=81=E5=88=9B=E5=BB=BA=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=96=87=E4=BB=B6=E7=9A=84=E6=9D=83=E9=99=90=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/repositories_controller.rb | 6 +++--- app/interactors/gitea/create_file_interactor.rb | 12 ++++++------ app/interactors/gitea/delete_file_interactor.rb | 13 +++++++------ app/interactors/gitea/update_file_interactor.rb | 13 +++++++------ .../gitea/repository/entries/create_service.rb | 13 +++++++------ .../gitea/repository/entries/delete_service.rb | 11 ++++++----- .../gitea/repository/entries/update_service.rb | 11 ++++++----- 7 files changed, 42 insertions(+), 37 deletions(-) diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index c8ab51cba..2e2711aa9 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -66,7 +66,7 @@ class RepositoriesController < ApplicationController end def create_file - interactor = Gitea::CreateFileInteractor.call(current_user, content_params) + interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @project.owner.login, content_params) if interactor.success? @file = interactor.result create_new_pr(params) @@ -76,7 +76,7 @@ class RepositoriesController < ApplicationController end def update_file - interactor = Gitea::UpdateFileInteractor.call(current_user, params.merge(identifier: @project.identifier)) + interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @project.owner.login, params.merge(identifier: @project.identifier)) if interactor.success? @file = interactor.result create_new_pr(params) @@ -87,7 +87,7 @@ class RepositoriesController < ApplicationController end def delete_file - interactor = Gitea::DeleteFileInteractor.call(current_user, params.merge(identifier: @project.identifier)) + interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @project.owner.login, params.merge(identifier: @project.identifier)) if interactor.success? @file = interactor.result render_result(1, "文件删除成功") diff --git a/app/interactors/gitea/create_file_interactor.rb b/app/interactors/gitea/create_file_interactor.rb index d8232379a..27a381e73 100644 --- a/app/interactors/gitea/create_file_interactor.rb +++ b/app/interactors/gitea/create_file_interactor.rb @@ -1,15 +1,15 @@ module Gitea class CreateFileInteractor - def self.call(user, params={}) - interactor = new(user, params) + def self.call(token, owner, params={}) + interactor = new(token, owner, params) interactor.run interactor end attr_reader :error, :result - def initialize(user, params) - @user = user + def initialize(token, owner, params) + @owner = owner @params = params end @@ -23,7 +23,7 @@ module Gitea def run Contents::CreateForm.new(valid_params).validate! - response = Gitea::Repository::Entries::CreateService.new(user, @params[:identifier], @params[:filepath], file_params).call + response = Gitea::Repository::Entries::CreateService.new(token, owner, @params[:identifier], @params[:filepath], file_params).call render_result(response) rescue Exception => exception Rails.logger.info "Exception ===========> #{exception.message}" @@ -33,7 +33,7 @@ module Gitea private - attr_reader :params, :user + attr_reader :params, :owner, :token def fail!(error) @error = error diff --git a/app/interactors/gitea/delete_file_interactor.rb b/app/interactors/gitea/delete_file_interactor.rb index d68988d19..9a48c9e56 100644 --- a/app/interactors/gitea/delete_file_interactor.rb +++ b/app/interactors/gitea/delete_file_interactor.rb @@ -1,15 +1,16 @@ module Gitea class DeleteFileInteractor - def self.call(user, params={}) - interactor = new(user, params) + def self.call(token, owner, params={}) + interactor = new(token, owner, params) interactor.run interactor end attr_reader :error, :result - def initialize(user, params) - @user = user + def initialize(token, owner, params) + @token = token + @owner = owner @params = params end @@ -23,7 +24,7 @@ module Gitea def run Contents::DeleteForm.new(valid_params).validate! - response = Gitea::Repository::Entries::DeleteService.new(user, @params[:identifier], @params[:filepath], file_params).call + response = Gitea::Repository::Entries::DeleteService.new(token, owner, @params[:identifier], @params[:filepath], file_params).call render_result(response) rescue Exception => exception fail!(exception.message) @@ -31,7 +32,7 @@ module Gitea private - attr_reader :params, :user + attr_reader :params, :owner, :token def fail!(error) puts "[exception]: error" diff --git a/app/interactors/gitea/update_file_interactor.rb b/app/interactors/gitea/update_file_interactor.rb index af895d481..7dc0c017f 100644 --- a/app/interactors/gitea/update_file_interactor.rb +++ b/app/interactors/gitea/update_file_interactor.rb @@ -1,15 +1,16 @@ module Gitea class UpdateFileInteractor - def self.call(user, params={}) - interactor = new(user, params) + def self.call(token, owner, params={}) + interactor = new(token, owner, params) interactor.run interactor end attr_reader :error, :result - def initialize(user, params) - @user = user + def initialize(token, owner, params) + @owner = owner + @token = token @params = params end @@ -23,7 +24,7 @@ module Gitea def run Contents::UpdateForm.new(valid_params).validate! - response = Gitea::Repository::Entries::UpdateService.new(user, @params[:identifier], @params[:filepath], file_params).call + response = Gitea::Repository::Entries::UpdateService.new(token, owner, @params[:identifier], @params[:filepath], file_params).call render_result(response) rescue Exception => exception fail!(exception.message) @@ -31,7 +32,7 @@ module Gitea private - attr_reader :params, :user + attr_reader :params, :owner, :token def fail!(error) puts "[exception]: error" diff --git a/app/services/gitea/repository/entries/create_service.rb b/app/services/gitea/repository/entries/create_service.rb index 62514fadb..86dd3b2fc 100644 --- a/app/services/gitea/repository/entries/create_service.rb +++ b/app/services/gitea/repository/entries/create_service.rb @@ -1,5 +1,5 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService - attr_reader :user, :repo_name, :filepath, :body + attr_reader :token, :owner, :repo_name, :filepath, :body # ref: The name of the commit/branch/tag. Default the repository’s default branch (usually master) # filepath: path of the dir, file, symlink or submodule in the repo @@ -20,11 +20,12 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService # "new_branch": "string" # } # - def initialize(user, repo_name, filepath, body) - @user = user + def initialize(token, owner, repo_name, filepath, body) + @token = token + @owner = owner @repo_name = repo_name @filepath = filepath - @body = body + @body = bodys end def call @@ -33,11 +34,11 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService private def params - Hash.new.merge(token: user.gitea_token, data: body) + Hash.new.merge(token: token, data: body) end def url - "/repos/#{user.login}/#{repo_name}/contents/#{filepath}".freeze + "/repos/#{owner}/#{repo_name}/contents/#{filepath}".freeze end end diff --git a/app/services/gitea/repository/entries/delete_service.rb b/app/services/gitea/repository/entries/delete_service.rb index cfc79a5b6..f9f412903 100644 --- a/app/services/gitea/repository/entries/delete_service.rb +++ b/app/services/gitea/repository/entries/delete_service.rb @@ -1,5 +1,5 @@ class Gitea::Repository::Entries::DeleteService < Gitea::ClientService - attr_reader :user, :repo_name, :filepath, :body + attr_reader :token, :owner, :repo_name, :filepath, :body # ref: The name of the commit/branch/tag. Default the repository’s default branch (usually master) # filepath: path of the dir, file, symlink or submodule in the repo @@ -19,8 +19,9 @@ class Gitea::Repository::Entries::DeleteService < Gitea::ClientService # "new_branch": "string", # "sha": "string", #require # } - def initialize(user, repo_name, filepath, body) - @user = user + def initialize(token, owner, repo_name, filepath, body) + @token = token + @owner = owner @repo_name = repo_name @filepath = filepath @body = body @@ -32,11 +33,11 @@ class Gitea::Repository::Entries::DeleteService < Gitea::ClientService private def params - Hash.new.merge(token: user.gitea_token, data: body) + Hash.new.merge(token: token, data: body) end def url - "/repos/#{user.login}/#{repo_name}/contents/#{filepath}".freeze + "/repos/#{owner}/#{repo_name}/contents/#{filepath}".freeze end end diff --git a/app/services/gitea/repository/entries/update_service.rb b/app/services/gitea/repository/entries/update_service.rb index 3f0ddf944..dadabc388 100644 --- a/app/services/gitea/repository/entries/update_service.rb +++ b/app/services/gitea/repository/entries/update_service.rb @@ -1,5 +1,5 @@ class Gitea::Repository::Entries::UpdateService < Gitea::ClientService - attr_reader :user, :repo_name, :filepath, :body + attr_reader :token, :owner, :repo_name, :filepath, :body # ref: The name of the commit/branch/tag. Default the repository’s default branch (usually master) # filepath: path of the dir, file, symlink or submodule in the repo @@ -20,8 +20,9 @@ class Gitea::Repository::Entries::UpdateService < Gitea::ClientService # "new_branch": "string" # } # - def initialize(user, repo_name, filepath, body) - @user = user + def initialize(token, owner, repo_name, filepath, body) + @token = token + @owner = owner @repo_name = repo_name @filepath = filepath @body = body @@ -33,11 +34,11 @@ class Gitea::Repository::Entries::UpdateService < Gitea::ClientService private def params - Hash.new.merge(token: user.gitea_token, data: body) + Hash.new.merge(token: token, data: body) end def url - "/repos/#{user.login}/#{repo_name}/contents/#{filepath}".freeze + "/repos/#{owner}/#{repo_name}/contents/#{filepath}".freeze end end From 28c7199ce669df62920799c8ab8f0e2994e76609 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 11:38:05 +0800 Subject: [PATCH 21/47] FIX bug --- app/services/gitea/repository/entries/create_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/gitea/repository/entries/create_service.rb b/app/services/gitea/repository/entries/create_service.rb index 86dd3b2fc..7f1a6b529 100644 --- a/app/services/gitea/repository/entries/create_service.rb +++ b/app/services/gitea/repository/entries/create_service.rb @@ -25,7 +25,7 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService @owner = owner @repo_name = repo_name @filepath = filepath - @body = bodys + @body = body end def call From 425a3743322d7d2af6bdca332dad050da05e1af9 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 14:40:35 +0800 Subject: [PATCH 22/47] ADD fork info for repositories show action --- app/views/repositories/show.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/repositories/show.json.jbuilder b/app/views/repositories/show.json.jbuilder index 0298c5a94..539f713a8 100644 --- a/app/views/repositories/show.json.jbuilder +++ b/app/views/repositories/show.json.jbuilder @@ -31,6 +31,7 @@ json.fork_info do if @fork_project.present? json.fork_form_name @fork_project.try(:name) json.fork_project_user_login @fork_project_user.try(:login) + json.fork_project_identifier @fork_project.identifier json.fork_project_user_name @fork_project_user.try(:show_real_name) end end From 7a2b496d953622b73e8babf725571d70cb1f009b Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 15:58:07 +0800 Subject: [PATCH 23/47] =?UTF-8?q?FIX=20=E5=8F=96=E6=B6=88=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=82=B9=E8=B5=9E=E3=80=81=E5=85=B3=E6=B3=A8=E3=80=81?= =?UTF-8?q?fork=E7=AD=89=E5=88=97=E8=A1=A8=E9=9C=80=E8=A6=81=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=9A=84=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 97753a5f5..b810e8298 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -2,7 +2,7 @@ class ProjectsController < ApplicationController include ApplicationHelper include OperateProjectAbilityAble include ProjectsHelper - before_action :require_login, except: %i[index branches group_type_list simple show] + before_action :require_login, except: %i[index branches group_type_list simple show fork_users praise_users watch_users] before_action :load_project, except: %i[index group_type_list migrate] before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] From 622e5512718420e322293779b748ce1f8d44cc6d Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 16:06:07 +0800 Subject: [PATCH 24/47] FIX repository edit routes bug --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 7012142cb..1cfe1b2a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -237,7 +237,7 @@ Rails.application.routes.draw do ) end - resource :projects, path: '/', except: [:show] do + resource :projects, path: '/', except: [:show, :edit] do member do get :branches get :simple From 8d46eecc2c6bb73430f40b0e9889ba5183f7b1cc Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 16:32:23 +0800 Subject: [PATCH 25/47] Delete create labels for gitea platform --- app/controllers/issue_tags_controller.rb | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/controllers/issue_tags_controller.rb b/app/controllers/issue_tags_controller.rb index fa12aefae..312de7842 100644 --- a/app/controllers/issue_tags_controller.rb +++ b/app/controllers/issue_tags_controller.rb @@ -38,12 +38,12 @@ class IssueTagsController < ApplicationController begin issue_tag = IssueTag.new(tag_params.merge(project_id: @project.id, user_id: current_user.id)) if issue_tag.save - gitea_tag = Gitea::Labels::CreateService.new(current_user, @repository.try(:identifier), tag_params).call - if gitea_tag && issue_tag.update_attributes(gid: gitea_tag["id"], gitea_url: gitea_tag["url"]) - normal_status(0, "标签创建成功") - else - normal_status(-1, "标签创建失败") - end + # gitea_tag = Gitea::Labels::CreateService.new(current_user, @repository.try(:identifier), tag_params).call + # if gitea_tag && issue_tag.update_attributes(gid: gitea_tag["id"], gitea_url: gitea_tag["url"]) + # normal_status(0, "标签创建成功") + # else + # normal_status(-1, "标签创建失败") + # end else normal_status(-1, "标签创建失败") end @@ -79,12 +79,12 @@ class IssueTagsController < ApplicationController ActiveRecord::Base.transaction do begin if @issue_tag.update_attributes(tag_params) - gitea_tag = Gitea::Labels::UpdateService.new(current_user, @repository.try(:identifier),@issue_tag.try(:gid), tag_params).call - if gitea_tag - normal_status(0, "标签更新成功") - else - normal_status(-1, "标签更新失败") - end + # gitea_tag = Gitea::Labels::UpdateService.new(current_user, @repository.try(:identifier),@issue_tag.try(:gid), tag_params).call + # if gitea_tag + # normal_status(0, "标签更新成功") + # else + # normal_status(-1, "标签更新失败") + # end else normal_status(-1, "标签更新失败") end @@ -103,12 +103,12 @@ class IssueTagsController < ApplicationController ActiveRecord::Base.transaction do begin if @issue_tag.destroy - issue_tag = Gitea::Labels::DeleteService.new(@user, @repository.try(:identifier), @issue_tag.try(:gid)).call - if issue_tag - normal_status(0, "标签删除成功") - else - normal_status(-1, "标签删除失败") - end + # issue_tag = Gitea::Labels::DeleteService.new(@user, @repository.try(:identifier), @issue_tag.try(:gid)).call + # if issue_tag + # normal_status(0, "标签删除成功") + # else + # normal_status(-1, "标签删除失败") + # end else normal_status(-1, "标签删除失败") end From 5bd8079386c898e3327652951657d7e444404815 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 17:15:54 +0800 Subject: [PATCH 26/47] FIX create project bug --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b810e8298..f46945f80 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -3,7 +3,7 @@ class ProjectsController < ApplicationController include OperateProjectAbilityAble include ProjectsHelper before_action :require_login, except: %i[index branches group_type_list simple show fork_users praise_users watch_users] - before_action :load_project, except: %i[index group_type_list migrate] + before_action :load_project, except: %i[index group_type_list migrate create] before_action :authorizate_user_can_edit_project!, only: %i[update] before_action :project_public?, only: %i[fork_users praise_users watch_users] From 8e7c25b5ed4826d98c1dc367e673ba4d5988edf4 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 21:11:33 +0800 Subject: [PATCH 27/47] FIX code bug --- app/models/user.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 68e3fb2e5..bbc5d15ac 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -78,14 +78,9 @@ class User < ApplicationRecord # 关注 has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注 has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户 -<<<<<<< HEAD has_many :watchers, as: :watchable, dependent: :destroy has_one :dev_ops_cloud_account, class_name: 'DevOps::CloudAccount', dependent: :destroy -======= - - has_many :watchers, as: :watchable, dependent: :destroy ->>>>>>> dev_change_route # 认证 has_many :apply_user_authentication From cf43a64adaedaf8336568814ce67191fbf131cfc Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Thu, 13 Aug 2020 23:38:32 +0800 Subject: [PATCH 28/47] FIX rewrite devops with ci --- app/controllers/ci/base_controller.rb | 3 + app/controllers/ci/builds_controller.rb | 58 ++++++++++ .../cloud_accounts_controller.rb | 21 ++-- .../{dev_ops => ci}/languages_controller.rb | 9 +- app/controllers/concerns/devopsable.rb | 6 +- app/controllers/dev_ops/builds_controller.rb | 59 ---------- .../create_cloud_account_form.rb | 2 +- app/libs/{dev_ops => ci}/drone/api.rb | 2 +- app/libs/{dev_ops => ci}/drone/ci.rb | 2 +- app/libs/{dev_ops => ci}/drone/client.rb | 2 +- app/libs/{dev_ops => ci}/drone/error.rb | 2 +- app/libs/ci/drone/request.rb | 108 ++++++++++++++++++ app/libs/{dev_ops => ci}/drone/server.rb | 2 +- app/libs/{dev_ops => ci}/drone/start.rb | 2 +- app/libs/dev_ops/drone/request.rb | 108 ------------------ app/models/{dev_ops => ci}/cloud_account.rb | 2 +- app/models/{dev_ops => ci}/language.rb | 2 +- app/models/project.rb | 2 +- app/models/repository.rb | 2 +- app/models/user.rb | 3 +- config/routes.rb | 2 +- ...v_ops_cloud_account_to_ci_cloud_account.rb | 5 + ..._rename_dev_ops_language_to_ci_language.rb | 5 + 23 files changed, 210 insertions(+), 199 deletions(-) create mode 100644 app/controllers/ci/base_controller.rb create mode 100644 app/controllers/ci/builds_controller.rb rename app/controllers/{dev_ops => ci}/cloud_accounts_controller.rb (71%) rename app/controllers/{dev_ops => ci}/languages_controller.rb (50%) delete mode 100644 app/controllers/dev_ops/builds_controller.rb rename app/forms/{dev_ops => ci}/create_cloud_account_form.rb (88%) rename app/libs/{dev_ops => ci}/drone/api.rb (98%) rename app/libs/{dev_ops => ci}/drone/ci.rb (97%) rename app/libs/{dev_ops => ci}/drone/client.rb (96%) rename app/libs/{dev_ops => ci}/drone/error.rb (82%) create mode 100644 app/libs/ci/drone/request.rb rename app/libs/{dev_ops => ci}/drone/server.rb (98%) rename app/libs/{dev_ops => ci}/drone/start.rb (97%) delete mode 100644 app/libs/dev_ops/drone/request.rb rename app/models/{dev_ops => ci}/cloud_account.rb (89%) rename app/models/{dev_ops => ci}/language.rb (90%) create mode 100644 db/migrate/20200813144941_rename_dev_ops_cloud_account_to_ci_cloud_account.rb create mode 100644 db/migrate/20200813150315_rename_dev_ops_language_to_ci_language.rb diff --git a/app/controllers/ci/base_controller.rb b/app/controllers/ci/base_controller.rb new file mode 100644 index 000000000..edbd5b8a4 --- /dev/null +++ b/app/controllers/ci/base_controller.rb @@ -0,0 +1,3 @@ +class Ci::BaseController < ApplicationController + before_action :require_login +end diff --git a/app/controllers/ci/builds_controller.rb b/app/controllers/ci/builds_controller.rb new file mode 100644 index 000000000..de6a9b22d --- /dev/null +++ b/app/controllers/ci/builds_controller.rb @@ -0,0 +1,58 @@ +class Ci::BuildsController < Ci::BaseController + include RepositoriesHelper + + before_action :find_project, :find_cloud_account + before_action :find_cloud_account, except: :get_trustie_pipeline + before_action :ci_authorize! + + def index + result = Ci::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier).builds + + render json: result + end + + def detail + result = Ci::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier, number: params[:number]).build + + render json: result + end + + def restart + result = Ci::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier, number: params[:number]).restart + + render json: result + end + + def delete + result = Ci::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier, number: params[:number]).stop + render json: result + end + + def logs + result = Ci::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier, build: params[:number], stage: params[:stage], step: params[:step]).logs + + render json: result + end + + # get .trustie-pipeline.yml file + def get_trustie_pipeline + file_path_uri = URI.parse('.trustie-pipeline.yml') + interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: params[:ref] || "master") + if interactor.success? + file = interactor.result + return render json: {} if file[:status] + + json = {name: file['name'], path: file['path'], sha: file['sha'], content: render_decode64_content(file['content'])} + render json: json + end + end + + private + def find_project + @project = Project.find params[:project_id] + end + + def find_cloud_account + @cloud_account = @project.ci_cloud_account + end +end diff --git a/app/controllers/dev_ops/cloud_accounts_controller.rb b/app/controllers/ci/cloud_accounts_controller.rb similarity index 71% rename from app/controllers/dev_ops/cloud_accounts_controller.rb rename to app/controllers/ci/cloud_accounts_controller.rb index d2e849eb5..f4ab4ba3c 100644 --- a/app/controllers/dev_ops/cloud_accounts_controller.rb +++ b/app/controllers/ci/cloud_accounts_controller.rb @@ -1,21 +1,20 @@ -class DevOps::CloudAccountsController < ApplicationController +class Ci::CloudAccountsController < Ci::BaseController include Devopsable - before_action :require_login before_action :auto_load_project - before_action :devops_authorize! + before_action :ci_authorize! before_action :find_cloud_account, only: %i[activate] def create ActiveRecord::Base.transaction do - DevOps::CreateCloudAccountForm.new(devops_params).validate! + Ci::CreateCloudAccountForm.new(devops_params).validate! # 1. 保存华为云服务器帐号 - create_params = devops_params.merge(ip_num: IPAddr.new(devops_params[:ip_num]).to_i, secret: DevOps::CloudAccount.encrypted_secret(devops_params[:secret])) + create_params = devops_params.merge(ip_num: IPAddr.new(devops_params[:ip_num]).to_i, secret: Ci::CloudAccount.encrypted_secret(devops_params[:secret])) if cloud_account = @project.dev_ops_cloud_account return render_error('该仓库已绑定了云帐号.') else - cloud_account = DevOps::CloudAccount.new(create_params) + cloud_account = Ci::CloudAccount.new(create_params) cloud_account.user = current_user cloud_account.save! end @@ -35,15 +34,15 @@ class DevOps::CloudAccountsController < ApplicationController logger.info "######### rpc_secret: #{rpc_secret}" # 3. 创建drone server - drone_server_cmd = DevOps::Drone::Server.new(oauth.client_id, oauth.client_secret, cloud_account.drone_host, rpc_secret).generate_cmd + drone_server_cmd = Ci::Drone::Server.new(oauth.client_id, oauth.client_secret, cloud_account.drone_host, rpc_secret).generate_cmd logger.info "######### drone_server_cmd: #{drone_server_cmd}" # 4. 创建drone client - drone_client_cmd = DevOps::Drone::Client.new(oauth.client_id, cloud_account.drone_ip, rpc_secret).generate_cmd + drone_client_cmd = Ci::Drone::Client.new(oauth.client_id, cloud_account.drone_ip, rpc_secret).generate_cmd logger.info "######### drone_client_cmd: #{drone_client_cmd}" # 5. 登录远程服务器,启动drone服务 - result = DevOps::Drone::Start.new(cloud_account.account, cloud_account.visible_secret, cloud_account.drone_ip, drone_server_cmd, drone_client_cmd).run + result = Ci::Drone::Start.new(cloud_account.account, cloud_account.visible_secret, cloud_account.drone_ip, drone_server_cmd, drone_client_cmd).run logger.info "######### result: #{result}" @@ -66,11 +65,11 @@ class DevOps::CloudAccountsController < ApplicationController result = if current_user.devops_has_token? # 已有drone_token的,直接激活项目 - DevOps::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier).activate + Ci::Drone::API.new(@cloud_account.drone_token, @cloud_account.drone_url, @project.owner.login, @project.identifier).activate else # 没有token,说明是第一次激活devops, 需要用户填写token值 return render_error('请先在CI服务端做用户认证.') if !current_user.devops_verified? - DevOps::Drone::API.new(params[:drone_token], @cloud_account.drone_url, @project.owner.login, @project.identifier).activate + Ci::Drone::API.new(params[:drone_token], @cloud_account.drone_url, @project.owner.login, @project.identifier).activate end if result diff --git a/app/controllers/dev_ops/languages_controller.rb b/app/controllers/ci/languages_controller.rb similarity index 50% rename from app/controllers/dev_ops/languages_controller.rb rename to app/controllers/ci/languages_controller.rb index be8b25b85..b4b3e184d 100644 --- a/app/controllers/dev_ops/languages_controller.rb +++ b/app/controllers/ci/languages_controller.rb @@ -1,21 +1,20 @@ -class DevOps::LanguagesController < ApplicationController +class Ci::LanguagesController < Ci::BaseController # TODO 需要开启权限认证,只有该项目devops初始化成功后才能获取语言列表 - before_action :require_login before_action :find_langugae, only: :show def index - @languages = DevOps::Language.by_usage_amount_desc + @languages = Ci::Language.by_usage_amount_desc end def show end def common - @languages = DevOps::Language.six_common + @languages = Ci::Language.six_common end private def find_langugae - @language = DevOps::Language.find params[:id] + @language = Ci::Language.find params[:id] end end diff --git a/app/controllers/concerns/devopsable.rb b/app/controllers/concerns/devopsable.rb index 43828fd22..ea6a48f16 100644 --- a/app/controllers/concerns/devopsable.rb +++ b/app/controllers/concerns/devopsable.rb @@ -4,8 +4,8 @@ module Devopsable included do end - # devops 权限验证 - def devops_authorize! + # ci 权限验证 + def ci_authorize! render_forbidden unless @project.owner?(current_user) end @@ -21,7 +21,7 @@ module Devopsable end def find_cloud_account - @cloud_account = DevOps::CloudAccount.find params[:id] + @cloud_account = Ci::CloudAccount.find params[:id] end def set_drone_token!(user, cloud_account, drone_token) diff --git a/app/controllers/dev_ops/builds_controller.rb b/app/controllers/dev_ops/builds_controller.rb deleted file mode 100644 index 8cbb48e35..000000000 --- a/app/controllers/dev_ops/builds_controller.rb +++ /dev/null @@ -1,59 +0,0 @@ -class DevOps::BuildsController < ApplicationController - include RepositoriesHelper - - before_action :require_login - before_action :find_project - before_action :devops_authorize! - - def index - cloud_account = @project.dev_ops_cloud_account - result = DevOps::Drone::API.new(cloud_account.drone_token, cloud_account.drone_url, @project.owner.login, @project.identifier).builds - - render json: result - end - - def detail - cloud_account = @project.dev_ops_cloud_account - result = DevOps::Drone::API.new(cloud_account.drone_token, cloud_account.drone_url, @project.owner.login, @project.identifier, number: params[:number]).build - - render json: result - end - - def restart - cloud_account = @project.dev_ops_cloud_account - result = DevOps::Drone::API.new(cloud_account.drone_token, cloud_account.drone_url, @project.owner.login, @project.identifier, number: params[:number]).restart - - render json: result - end - - def delete - cloud_account = @project.dev_ops_cloud_account - result = DevOps::Drone::API.new(cloud_account.drone_token, cloud_account.drone_url, @project.owner.login, @project.identifier, number: params[:number]).stop - render json: result - end - - def logs - cloud_account = @project.dev_ops_cloud_account - result = DevOps::Drone::API.new(cloud_account.drone_token, cloud_account.drone_url, @project.owner.login, @project.identifier, build: params[:number], stage: params[:stage], step: params[:step]).logs - - render json: result - end - - # get .trustie-pipeline.yml file - def get_trustie_pipeline - file_path_uri = URI.parse('.trustie-pipeline.yml') - interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: params[:ref] || "master") - if interactor.success? - file = interactor.result - return render json: {} if file[:status] - - json = {name: file['name'], path: file['path'], sha: file['sha'], content: render_decode64_content(file['content'])} - render json: json - end - end - - private - def find_project - @project = Project.find params[:project_id] - end -end diff --git a/app/forms/dev_ops/create_cloud_account_form.rb b/app/forms/ci/create_cloud_account_form.rb similarity index 88% rename from app/forms/dev_ops/create_cloud_account_form.rb rename to app/forms/ci/create_cloud_account_form.rb index 2d1752b74..a2dda6a4f 100644 --- a/app/forms/dev_ops/create_cloud_account_form.rb +++ b/app/forms/ci/create_cloud_account_form.rb @@ -1,4 +1,4 @@ -class DevOps::CreateCloudAccountForm +class Ci::CreateCloudAccountForm include ActiveModel::Model attr_accessor :project_id, :ip_num, :account, :secret diff --git a/app/libs/dev_ops/drone/api.rb b/app/libs/ci/drone/api.rb similarity index 98% rename from app/libs/dev_ops/drone/api.rb rename to app/libs/ci/drone/api.rb index c7d807860..0c83007a5 100644 --- a/app/libs/dev_ops/drone/api.rb +++ b/app/libs/ci/drone/api.rb @@ -1,4 +1,4 @@ -class DevOps::Drone::API < DevOps::Drone::Request +class Ci::Drone::API < Ci::Drone::Request attr_reader :drone_token, :endpoint, :owner, :repo, :options # drone_token: diff --git a/app/libs/dev_ops/drone/ci.rb b/app/libs/ci/drone/ci.rb similarity index 97% rename from app/libs/dev_ops/drone/ci.rb rename to app/libs/ci/drone/ci.rb index c2a06d4ce..c47b5396f 100644 --- a/app/libs/dev_ops/drone/ci.rb +++ b/app/libs/ci/drone/ci.rb @@ -1,4 +1,4 @@ -class DevOps::Drone::Ci +class Ci::Drone::Ci attr_reader :host, :username, :password, :gitea_username # host: drone server's ip diff --git a/app/libs/dev_ops/drone/client.rb b/app/libs/ci/drone/client.rb similarity index 96% rename from app/libs/dev_ops/drone/client.rb rename to app/libs/ci/drone/client.rb index c427f79d8..ebaef5b1c 100644 --- a/app/libs/dev_ops/drone/client.rb +++ b/app/libs/ci/drone/client.rb @@ -1,4 +1,4 @@ -class DevOps::Drone::Client +class Ci::Drone::Client attr_reader :client_id, :drone_ip, :rpc_secret # client_id: user's client_id from oauth diff --git a/app/libs/dev_ops/drone/error.rb b/app/libs/ci/drone/error.rb similarity index 82% rename from app/libs/dev_ops/drone/error.rb rename to app/libs/ci/drone/error.rb index 2803a6c88..2ddfccd8f 100644 --- a/app/libs/dev_ops/drone/error.rb +++ b/app/libs/ci/drone/error.rb @@ -1,4 +1,4 @@ -class DevOps::Drone::Error < StandardError +class Ci::Drone::Error < StandardError attr_reader :code def initialize(code, message) diff --git a/app/libs/ci/drone/request.rb b/app/libs/ci/drone/request.rb new file mode 100644 index 000000000..5f3e54be4 --- /dev/null +++ b/app/libs/ci/drone/request.rb @@ -0,0 +1,108 @@ +class Ci::Drone::Request + # Converts the response body to an ObjectifiedHash. + def self.parse(body) + body = decode(body) + + if body.is_a? Hash + ObjectifiedHash.new body + elsif body.is_a? Array + body.collect! { |e| ObjectifiedHash.new(e) } + elsif body == true + body + else + raise Error::Parsing.new "Couldn't parse a response body" + end + end + + # Decodes a JSON response into Ruby object. + def self.decode(response) + begin + JSON.load response + rescue JSON::ParserError + raise Error::Parsing.new "The response is not a valid JSON" + end + end + + def get(endpoint, path, options={}) + validate_request_params!(endpoint) + request(:get, endpoint, path, options) + end + + def post(endpoint, path, options={}) + validate_request_params!(endpoint) + request(:post, endpoint, path, options) + end + + def put(endpoint, path, options={}) + validate_request_params!(endpoint) + request(:put, endpoint, path, options) + end + + def patch(endpoint, path, options={}) + validate_request_params!(endpoint) + request(:patch, endpoint, path, options) + end + + def delete(endpoint, path, options={}) + validate_request_params!(endpoint) + request(:delete, endpoint, path, options) + end + + private + def request(method, endpoint, path, **params) + Rails.logger.info("[drone] request: #{method} #{path} #{params.except(:drone_token).inspect}") + + client ||= begin + Faraday.new(url: endpoint) do |req| + req.request :url_encoded + req.headers['Content-Type'] = 'application/json' + req.response :logger # 显示日志 + req.adapter Faraday.default_adapter + req.authorization :Bearer, params[:drone_token] + req.headers['Authorization'] + end + end + response = client.public_send(method, path) do |req| + req.body = params.except(:drone_token).to_json + end + + json_response(response) + end + + # Checks the response code for common errors. + # Returns parsed response for successful requests. + def validate(response) + # case response.code + # when 400; raise Error::BadRequest.new error_message(response) + # when 401; raise Error::Unauthorized.new error_message(response) + # when 403; raise Error::Forbidden.new error_message(response) + # when 404; raise Error::NotFound.new error_message(response) + # when 405; raise Error::MethodNotAllowed.new error_message(response) + # when 406; raise Error::DataNotAccepted.new error_message(response) + # when 409; raise Error::Conflict.new error_message(response) + # when 500; raise Error::InternalServerError.new error_message(response) + # when 502; raise Error::BadGateway.new error_message(response) + # when 503; raise Error::ServiceUnavailable.new error_message(response) + # end + + response.parsed_response + end + + # Checks a base_uri and params for requests. + def validate_request_params!(endpoint) + raise "Please set an endpoint to API" unless endpoint + end + + def error_message(response) + "Server responded with code #{response.code}, message: #{response.parsed_response.message}. " \ + "Request URI: #{response.request.base_uri}#{response.request.path}" + end + + def json_response(response) + result = JSON.parse(response.body) + status = response.status + Rails.logger.info("[drone] response:#{status} #{result.inspect}") + + response.status != 200 ? result.merge!(status: response.status) : result + end +end diff --git a/app/libs/dev_ops/drone/server.rb b/app/libs/ci/drone/server.rb similarity index 98% rename from app/libs/dev_ops/drone/server.rb rename to app/libs/ci/drone/server.rb index 152f27df4..2a350cc95 100644 --- a/app/libs/dev_ops/drone/server.rb +++ b/app/libs/ci/drone/server.rb @@ -1,4 +1,4 @@ -class DevOps::Drone::Server +class Ci::Drone::Server attr_reader :client_id, :client_secret, :drone_host, :rpc_secret # client_id: user's client_id from oauth diff --git a/app/libs/dev_ops/drone/start.rb b/app/libs/ci/drone/start.rb similarity index 97% rename from app/libs/dev_ops/drone/start.rb rename to app/libs/ci/drone/start.rb index 29c3324f6..19b7d09ce 100644 --- a/app/libs/dev_ops/drone/start.rb +++ b/app/libs/ci/drone/start.rb @@ -1,4 +1,4 @@ -class DevOps::Drone::Start +class Ci::Drone::Start attr_reader :drone_username, :drone_password, :drone_host, :drone_server_cmd, :drone_client_cmd # drone_username="XXXX" 云服务器登录用户名 diff --git a/app/libs/dev_ops/drone/request.rb b/app/libs/dev_ops/drone/request.rb deleted file mode 100644 index a59b515d2..000000000 --- a/app/libs/dev_ops/drone/request.rb +++ /dev/null @@ -1,108 +0,0 @@ - class DevOps::Drone::Request - # Converts the response body to an ObjectifiedHash. - def self.parse(body) - body = decode(body) - - if body.is_a? Hash - ObjectifiedHash.new body - elsif body.is_a? Array - body.collect! { |e| ObjectifiedHash.new(e) } - elsif body == true - body - else - raise Error::Parsing.new "Couldn't parse a response body" - end - end - - # Decodes a JSON response into Ruby object. - def self.decode(response) - begin - JSON.load response - rescue JSON::ParserError - raise Error::Parsing.new "The response is not a valid JSON" - end - end - - def get(endpoint, path, options={}) - validate_request_params!(endpoint) - request(:get, endpoint, path, options) - end - - def post(endpoint, path, options={}) - validate_request_params!(endpoint) - request(:post, endpoint, path, options) - end - - def put(endpoint, path, options={}) - validate_request_params!(endpoint) - request(:put, endpoint, path, options) - end - - def patch(endpoint, path, options={}) - validate_request_params!(endpoint) - request(:patch, endpoint, path, options) - end - - def delete(endpoint, path, options={}) - validate_request_params!(endpoint) - request(:delete, endpoint, path, options) - end - - private - def request(method, endpoint, path, **params) - Rails.logger.info("[drone] request: #{method} #{path} #{params.except(:drone_token).inspect}") - - client ||= begin - Faraday.new(url: endpoint) do |req| - req.request :url_encoded - req.headers['Content-Type'] = 'application/json' - req.response :logger # 显示日志 - req.adapter Faraday.default_adapter - req.authorization :Bearer, params[:drone_token] - req.headers['Authorization'] - end - end - response = client.public_send(method, path) do |req| - req.body = params.except(:drone_token).to_json - end - - json_response(response) - end - - # Checks the response code for common errors. - # Returns parsed response for successful requests. - def validate(response) - # case response.code - # when 400; raise Error::BadRequest.new error_message(response) - # when 401; raise Error::Unauthorized.new error_message(response) - # when 403; raise Error::Forbidden.new error_message(response) - # when 404; raise Error::NotFound.new error_message(response) - # when 405; raise Error::MethodNotAllowed.new error_message(response) - # when 406; raise Error::DataNotAccepted.new error_message(response) - # when 409; raise Error::Conflict.new error_message(response) - # when 500; raise Error::InternalServerError.new error_message(response) - # when 502; raise Error::BadGateway.new error_message(response) - # when 503; raise Error::ServiceUnavailable.new error_message(response) - # end - - response.parsed_response - end - - # Checks a base_uri and params for requests. - def validate_request_params!(endpoint) - raise "Please set an endpoint to API" unless endpoint - end - - def error_message(response) - "Server responded with code #{response.code}, message: #{response.parsed_response.message}. " \ - "Request URI: #{response.request.base_uri}#{response.request.path}" - end - - def json_response(response) - result = JSON.parse(response.body) - status = response.status - Rails.logger.info("[drone] response:#{status} #{result.inspect}") - - response.status != 200 ? result.merge!(status: response.status) : result - end - end diff --git a/app/models/dev_ops/cloud_account.rb b/app/models/ci/cloud_account.rb similarity index 89% rename from app/models/dev_ops/cloud_account.rb rename to app/models/ci/cloud_account.rb index 8b07a7493..18ac68fa7 100644 --- a/app/models/dev_ops/cloud_account.rb +++ b/app/models/ci/cloud_account.rb @@ -1,4 +1,4 @@ -class DevOps::CloudAccount < ApplicationRecord +class Ci::CloudAccount < ApplicationRecord belongs_to :project belongs_to :user belongs_to :repository, foreign_key: :repo_id diff --git a/app/models/dev_ops/language.rb b/app/models/ci/language.rb similarity index 90% rename from app/models/dev_ops/language.rb rename to app/models/ci/language.rb index 742ae54ea..0d16e5bd2 100644 --- a/app/models/dev_ops/language.rb +++ b/app/models/ci/language.rb @@ -1,4 +1,4 @@ -class DevOps::Language < ApplicationRecord +class Ci::Language < ApplicationRecord # before_save :encode_content belongs_to :cover, class_name: "Attachment", foreign_key: :cover_id, optional: true diff --git a/app/models/project.rb b/app/models/project.rb index 83ff78789..24bbe9afc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -20,7 +20,7 @@ class Project < ApplicationRecord has_many :fork_users, dependent: :destroy # has_many :commits, dependent: :destroy - has_one :dev_ops_cloud_account, class_name: 'DevOps::CloudAccount', dependent: :destroy + has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', dependent: :destroy has_one :project_score, dependent: :destroy has_one :repository, dependent: :destroy has_many :pull_requests, dependent: :destroy diff --git a/app/models/repository.rb b/app/models/repository.rb index 373604c8a..17c42a938 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -3,7 +3,7 @@ class Repository < ApplicationRecord belongs_to :project, :touch => true belongs_to :user has_one :mirror, foreign_key: :repo_id - has_one :dev_ops_cloud_account, class_name: 'DevOps::CloudAccount', foreign_key: :repo_id + has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', foreign_key: :repo_id has_many :version_releases, dependent: :destroy validates :identifier, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index bbc5d15ac..e860cbbe8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,6 +6,7 @@ class User < ApplicationRecord include BaseModel include ProjectOperable include ProjectAbility + include Droneable # include Searchable::Dependents::User # devops step @@ -80,7 +81,7 @@ class User < ApplicationRecord has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户 has_many :watchers, as: :watchable, dependent: :destroy - has_one :dev_ops_cloud_account, class_name: 'DevOps::CloudAccount', dependent: :destroy + has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', dependent: :destroy # 认证 has_many :apply_user_authentication diff --git a/config/routes.rb b/config/routes.rb index c343a3798..3d660421c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,7 +15,7 @@ Rails.application.routes.draw do get 'auth/cas/callback', to: 'oauth/cas#create' resources :edu_settings scope '/api' do - namespace :dev_ops do + namespace :ci do resources :cloud_accounts, only: [:create] do member do post :activate diff --git a/db/migrate/20200813144941_rename_dev_ops_cloud_account_to_ci_cloud_account.rb b/db/migrate/20200813144941_rename_dev_ops_cloud_account_to_ci_cloud_account.rb new file mode 100644 index 000000000..2fcc477ae --- /dev/null +++ b/db/migrate/20200813144941_rename_dev_ops_cloud_account_to_ci_cloud_account.rb @@ -0,0 +1,5 @@ +class RenameDevOpsCloudAccountToCiCloudAccount < ActiveRecord::Migration[5.2] + def change + rename_table :dev_ops_cloud_accounts, :ci_cloud_accounts + end +end diff --git a/db/migrate/20200813150315_rename_dev_ops_language_to_ci_language.rb b/db/migrate/20200813150315_rename_dev_ops_language_to_ci_language.rb new file mode 100644 index 000000000..473618389 --- /dev/null +++ b/db/migrate/20200813150315_rename_dev_ops_language_to_ci_language.rb @@ -0,0 +1,5 @@ +class RenameDevOpsLanguageToCiLanguage < ActiveRecord::Migration[5.2] + def change + rename_table :dev_ops_languages, :ci_languages + end +end From c1b0061b8dbf263f054e5aef7c80b4be4a753015 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 18:19:00 +0800 Subject: [PATCH 29/47] ADD educode user login --- app/assets/stylesheets/oauth.scss | 78 +++++++++++++++++++ app/controllers/accounts_controller.rb | 20 +---- app/controllers/application_controller.rb | 3 +- app/controllers/concerns/register_helper.rb | 28 +++++++ app/controllers/oauth/educoder_controller.rb | 36 +++++++++ app/controllers/oauth_controller.rb | 22 ++++++ app/forms/oauth_educoder_form.rb | 37 +++++++++ app/libs/oauth_educoder.rb | 20 +++++ app/models/open_users/educoder.rb | 9 +++ app/views/layouts/oauth_register.html.erb | 13 ++++ app/views/oauth/register.html.erb | 54 +++++++++++++ config/initializers/assets.rb | 3 +- config/routes.rb | 5 ++ public/images/oauth/backImg.png | Bin 0 -> 108035 bytes public/images/oauth/logo.png | Bin 0 -> 9887 bytes 15 files changed, 309 insertions(+), 19 deletions(-) create mode 100644 app/assets/stylesheets/oauth.scss create mode 100644 app/controllers/concerns/register_helper.rb create mode 100644 app/controllers/oauth/educoder_controller.rb create mode 100644 app/forms/oauth_educoder_form.rb create mode 100644 app/libs/oauth_educoder.rb create mode 100644 app/models/open_users/educoder.rb create mode 100644 app/views/layouts/oauth_register.html.erb create mode 100644 app/views/oauth/register.html.erb create mode 100644 public/images/oauth/backImg.png create mode 100644 public/images/oauth/logo.png diff --git a/app/assets/stylesheets/oauth.scss b/app/assets/stylesheets/oauth.scss new file mode 100644 index 000000000..e862b52de --- /dev/null +++ b/app/assets/stylesheets/oauth.scss @@ -0,0 +1,78 @@ +html{margin:0px;padding: 0px;font-size: 14px;font-family: "微软雅黑","宋体";} +body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { + margin: 0; + padding: 0; +} +.IndexContent{ + height: 100vh; + width: 100%; + position: relative; + background-image: url('/images/oauth/backImg.png'); + background-repeat: no-repeat; + background-size: cover; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.indexLogo{ + width:80px; + margin-bottom: 35px; +} +.indexPanel{ + width: 580px; + min-height: 400px; + background-color: #fff; + box-shadow: 0px 2px 10px 5px rgba(0,0,0,0.05); + border-radius: 5px; + box-sizing: border-box; +} +.indexTitle{ + height: 75px; + line-height: 75px; + font-size: 18px; + color:#333; + text-align: center; + border-bottom: 1px solid #eee; +} +.indexInfo{ + display: flex; + flex-direction: column; + align-items: flex-start; +} +.indexInfos{ + padding:40px 60px; +} +.indexInfo > span{ + color: #333; + font-size: 16px; + margin-top: 5px; +} +.indexInfo input{ + width: 100%; + height:40px; + border-radius: 2px; + border:1px solid #eee; + margin-top: 5px; + padding:0px 0px 0px 8px; + outline: none; +} +.indexInfo .checkInfo{ + height: 15px; + color: red; +} +.indexBtn{ + text-align: center; + margin-top: 20px; +} +.indexSubmit{ + width: 50%; + height: 32px; + line-height: 32px; + background-color: #1890FF; + border:none; + color: #fff; + border-radius: 2px; + cursor: pointer; + outline: none; +} diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 23290d91c..8d51fddc8 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -13,24 +13,12 @@ class AccountsController < ApplicationController password = params[:password] platform = (params[:platform] || 'forge')&.gsub(/\s+/, "") - @user = User.new(admin: false, login: username, mail: email, type: "User") - @user.password = password - @user.platform = platform - @user.activate - ActiveRecord::Base.transaction do - interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password}) - if interactor.success? - gitea_user = interactor.result - result = Gitea::User::GenerateTokenService.new(username, password).call - @user.gitea_token = result['sha1'] - @user.gitea_uid = gitea_user['id'] - if @user.save! - UserExtension.create!(user_id: @user.id) - render_ok({user: {id: @user.id, token: @user.gitea_token}}) - end + result = autologin_register(username, email, password, platform) + if result[:message].blank? + render_ok({user: result[:user]}) else - render_error(interactor.error) + render_error(result[:message]) end end rescue Exception => e diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 159832177..cd317e50f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,7 +9,8 @@ class ApplicationController < ActionController::Base include GitHelper include LoggerHelper include LoginHelper - + include RegisterHelper + protect_from_forgery prepend: true, unless: -> { request.format.json? } before_action :check_sign diff --git a/app/controllers/concerns/register_helper.rb b/app/controllers/concerns/register_helper.rb new file mode 100644 index 000000000..a5a5652ba --- /dev/null +++ b/app/controllers/concerns/register_helper.rb @@ -0,0 +1,28 @@ +module RegisterHelper + extend ActiveSupport::Concern + + def autologin_register(username, email, password, platform= '') + result = {message: nil, user: nil} + + user = User.new(admin: false, login: username, mail: email, type: "User") + user.password = password + user.platform = platform + user.activate + + interactor = Gitea::RegisterInteractor.call({username: username, email: email, password: password}) + if interactor.success? + gitea_user = interactor.result + result = Gitea::User::GenerateTokenService.new(username, password).call + user.gitea_token = result['sha1'] + user.gitea_uid = gitea_user['id'] + if user.save! + UserExtension.create!(user_id: user.id) + result[:user] = {id: user.id, token: user.gitea_token} + end + else + result[:message] = interactor.error + end + result + end + +end diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb new file mode 100644 index 000000000..17c5e5dc6 --- /dev/null +++ b/app/controllers/oauth/educoder_controller.rb @@ -0,0 +1,36 @@ +class Oauth::EducoderController < Oauth::BaseController + def bind + begin + login = params[:login] + callback_url = params[:callback_url] + oauth_token = params[:key] + raw_pay_load = params[:raw_pay_load] + + ::OauthEducoderForm.new({login: login, oauth_token: oauth_token, callback_url: callback_url, raw_pay_load: raw_pay_load}).validate! + + open_user= OpenUser::Educoder.find_by(uid: login) + + if open_user.present? && open_user.user.present? && open_user.user.email_bind? + # 存在说明绑定了,验证信息是否齐全, + if current_user != open_user.user + logout_user + successful_authentication(open_user.user) + end + + redirect_to callback_url + else + # 未存在需要进行绑定 + if current_user.blank? || !current_user.logged? + # forge平台未登录 + redirect_to oauth_register_path(user_id: login, callback_url: callback_url) + else + # forge平台已登录 + OpenUsers::Educoder.create!(user: current_user, uid: login) + redirect_to callback_url + end + end + rescue WechatOauth::Error => ex + render_error(ex.message) + end + end +end diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb index ff5908cd0..f9c526fac 100644 --- a/app/controllers/oauth_controller.rb +++ b/app/controllers/oauth_controller.rb @@ -1,4 +1,6 @@ class OauthController < ApplicationController + layout "oauth_register", only: [:register] + DEFAULT_PASSWORD = "a12345678" TOKEN_CALL_BACK = "/oauth/get_token_callback" USER_INFO = "/oauth/userinfo" @@ -51,4 +53,24 @@ class OauthController < ApplicationController def get_token_callback end + + def register + # redirect_to params[:callback_url] + end + + def auto_register + login = params[:login] + email = params[:email] + password = params[:login] + platform = params[:plathform] || 'forge' + + result = autologin_register(login, email, password, platform) + + if result[:message].blank? + redirect_to params[:callback_url] + else + render :action => "auto_register" + end + end + end diff --git a/app/forms/oauth_educoder_form.rb b/app/forms/oauth_educoder_form.rb new file mode 100644 index 000000000..2a6a0c385 --- /dev/null +++ b/app/forms/oauth_educoder_form.rb @@ -0,0 +1,37 @@ +class OauthEducoderForm + include ActiveModel::Model + + attr_accessor :login, :oauth_token, :callback_url, :raw_pay_load + + validates :login, presence: true + validates :oauth_token, presence: true + validates :callback_url, presence: true + validates :raw_pay_load, presence: true + + validate :check_oauth_token! + validate :check_callback_url! + + def checke_raw_pay_load! + secret = OauthEducoder.config[:access_key_secret] + + before_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60-1}" + now_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60-1}" + + if raw_pay_load != Digest::SHA1.hexdigest(now_raw_pay_load) || raw_pay_load != Digest::SHA1.hexdigest(before_raw_pay_load) + raise '你的请求无效值无效.' + end + end + + def checke_raw_pay_load! + secret = OauthEducoder.config[:access_key_secret] + raise 'oauth_token值无效.' if oauth_token != secret + end + + def check_callback_url! + request_host = URI.parse(callback_url).host + callback_url = OauthEducoder.config[:callback_url_host] + + raise 'callback_url参数无效.' if request_host != callback_url + end + +end diff --git a/app/libs/oauth_educoder.rb b/app/libs/oauth_educoder.rb new file mode 100644 index 000000000..4e700d76d --- /dev/null +++ b/app/libs/oauth_educoder.rb @@ -0,0 +1,20 @@ +module OauthEducoder + class << self + def config + educoder_config = {} + + begin + config = Rails.application.config_for(:configuration).symbolize_keys! + educoder_config = config[:oauth][:educoder].symbolize_keys! + raise 'oauth educoder config missing' if educoder_config.blank? + rescue => ex + raise ex if Rails.env.production? + + puts %Q{\033[33m [warning] educoder config or configuration.yml missing, + please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m} + educoder_config = {} + end + educoder_config + end + end +end diff --git a/app/models/open_users/educoder.rb b/app/models/open_users/educoder.rb new file mode 100644 index 000000000..8993fe59f --- /dev/null +++ b/app/models/open_users/educoder.rb @@ -0,0 +1,9 @@ +class OpenUsers::EduCoder < OpenUser + def nickname + extra&.[]('nickname') + end + + def en_type + 'educoder' + end +end diff --git a/app/views/layouts/oauth_register.html.erb b/app/views/layouts/oauth_register.html.erb new file mode 100644 index 000000000..ac381eb4f --- /dev/null +++ b/app/views/layouts/oauth_register.html.erb @@ -0,0 +1,13 @@ + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <%= stylesheet_link_tag 'oauth', media: 'all','data-turbolinks-track': 'reload' %> + + +
+ <%= image_tag('/images/oauth/logo.png') %> + <%= yield %> +
+ + diff --git a/app/views/oauth/register.html.erb b/app/views/oauth/register.html.erb new file mode 100644 index 000000000..4b053b542 --- /dev/null +++ b/app/views/oauth/register.html.erb @@ -0,0 +1,54 @@ +
+

完善信息,进入比赛

+
+ <%= form_tag(oauth_auto_register_path, method: :post, class: 'form-inline search-form flex-1') do %> + <%= hidden_field_tag 'callback_url', params[:callback_url] %> +
+ 用户名: + <%= text_field_tag :mail, params[:login], placeholder: '请输入用户名', disabled: true, id: 'login' %> +

+
+
+ 邮箱: + <%= text_field_tag :mail, '', placeholder: '请输入绑定邮箱', maxlength: 40, id: 'email' %> +

+
+
+ 密码: + <%= password_field_tag :password, '', placeholder: '请输入账号密码', id: 'password' %> +

+
+
+ +
+ <% end %> +
+
+ diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index ef056bbef..60855f79e 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -12,5 +12,4 @@ Rails.application.config.assets.paths << Rails.root.join('vendor/assets') # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. -Rails.application.config.assets.precompile += %w( admin.js admin.css college.js college.css cooperative.js cooperative.css ) - +Rails.application.config.assets.precompile += %w( admin.js admin.css college.js college.css cooperative.js cooperative.css oauth.css ) diff --git a/config/routes.rb b/config/routes.rb index 1cfe1b2a3..00e27ed7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,11 @@ Rails.application.routes.draw do get 'auth/qq/callback', to: 'oauth/qq#create' get 'auth/failure', to: 'oauth/base#auth_failure' get 'auth/cas/callback', to: 'oauth/cas#create' + + get 'oauth/bind', to: 'oauth/educoder#bind' + get 'oauth/register', to: 'oauth#register' + post 'oauth/auto_register', to: 'oauth#auto_register' + resources :edu_settings scope '/api' do diff --git a/public/images/oauth/backImg.png b/public/images/oauth/backImg.png new file mode 100644 index 0000000000000000000000000000000000000000..450002fac841da432b15d1ad886c64eaa15278fc GIT binary patch literal 108035 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 literal 0 HcmV?d00001 diff --git a/public/images/oauth/logo.png b/public/images/oauth/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4d0b489bf11ffd8c1c90be3161209b95b8c069a0 GIT binary patch literal 9887 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 Date: Fri, 14 Aug 2020 18:40:05 +0800 Subject: [PATCH 30/47] FIX oauth css for public dir --- app/views/layouts/oauth_register.html.erb | 2 +- public/stylesheets/css/oauth.css | 78 +++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 public/stylesheets/css/oauth.css diff --git a/app/views/layouts/oauth_register.html.erb b/app/views/layouts/oauth_register.html.erb index ac381eb4f..cb1e4ef47 100644 --- a/app/views/layouts/oauth_register.html.erb +++ b/app/views/layouts/oauth_register.html.erb @@ -2,7 +2,7 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= stylesheet_link_tag 'oauth', media: 'all','data-turbolinks-track': 'reload' %> + <%= stylesheet_link_tag '/stylesheets/css/oauth', '', :media => 'all' %>
diff --git a/public/stylesheets/css/oauth.css b/public/stylesheets/css/oauth.css new file mode 100644 index 000000000..e862b52de --- /dev/null +++ b/public/stylesheets/css/oauth.css @@ -0,0 +1,78 @@ +html{margin:0px;padding: 0px;font-size: 14px;font-family: "微软雅黑","宋体";} +body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { + margin: 0; + padding: 0; +} +.IndexContent{ + height: 100vh; + width: 100%; + position: relative; + background-image: url('/images/oauth/backImg.png'); + background-repeat: no-repeat; + background-size: cover; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.indexLogo{ + width:80px; + margin-bottom: 35px; +} +.indexPanel{ + width: 580px; + min-height: 400px; + background-color: #fff; + box-shadow: 0px 2px 10px 5px rgba(0,0,0,0.05); + border-radius: 5px; + box-sizing: border-box; +} +.indexTitle{ + height: 75px; + line-height: 75px; + font-size: 18px; + color:#333; + text-align: center; + border-bottom: 1px solid #eee; +} +.indexInfo{ + display: flex; + flex-direction: column; + align-items: flex-start; +} +.indexInfos{ + padding:40px 60px; +} +.indexInfo > span{ + color: #333; + font-size: 16px; + margin-top: 5px; +} +.indexInfo input{ + width: 100%; + height:40px; + border-radius: 2px; + border:1px solid #eee; + margin-top: 5px; + padding:0px 0px 0px 8px; + outline: none; +} +.indexInfo .checkInfo{ + height: 15px; + color: red; +} +.indexBtn{ + text-align: center; + margin-top: 20px; +} +.indexSubmit{ + width: 50%; + height: 32px; + line-height: 32px; + background-color: #1890FF; + border:none; + color: #fff; + border-radius: 2px; + cursor: pointer; + outline: none; +} From 2199258f8e82f50cab889bac23b33de06be71903 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 19:47:02 +0800 Subject: [PATCH 31/47] FIX bug --- app/controllers/oauth/educoder_controller.rb | 5 ++--- app/forms/oauth_educoder_form.rb | 18 ++++++------------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb index 17c5e5dc6..2d1bef189 100644 --- a/app/controllers/oauth/educoder_controller.rb +++ b/app/controllers/oauth/educoder_controller.rb @@ -3,10 +3,9 @@ class Oauth::EducoderController < Oauth::BaseController begin login = params[:login] callback_url = params[:callback_url] - oauth_token = params[:key] - raw_pay_load = params[:raw_pay_load] + token = params[:token] - ::OauthEducoderForm.new({login: login, oauth_token: oauth_token, callback_url: callback_url, raw_pay_load: raw_pay_load}).validate! + ::OauthEducoderForm.new({login: login, token: token, callback_url: callback_url}).validate! open_user= OpenUser::Educoder.find_by(uid: login) diff --git a/app/forms/oauth_educoder_form.rb b/app/forms/oauth_educoder_form.rb index 2a6a0c385..2a9cf32e9 100644 --- a/app/forms/oauth_educoder_form.rb +++ b/app/forms/oauth_educoder_form.rb @@ -1,32 +1,26 @@ class OauthEducoderForm include ActiveModel::Model - attr_accessor :login, :oauth_token, :callback_url, :raw_pay_load + attr_accessor :login, :token, :callback_url validates :login, presence: true - validates :oauth_token, presence: true + validates :token, presence: true validates :callback_url, presence: true - validates :raw_pay_load, presence: true - validate :check_oauth_token! validate :check_callback_url! + valitate :check_auth! - def checke_raw_pay_load! + def check_auth! secret = OauthEducoder.config[:access_key_secret] before_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60-1}" - now_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60-1}" + now_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60}" - if raw_pay_load != Digest::SHA1.hexdigest(now_raw_pay_load) || raw_pay_load != Digest::SHA1.hexdigest(before_raw_pay_load) + if token != Digest::SHA1.hexdigest(now_raw_pay_load) || token != Digest::SHA1.hexdigest(before_raw_pay_load) raise '你的请求无效值无效.' end end - def checke_raw_pay_load! - secret = OauthEducoder.config[:access_key_secret] - raise 'oauth_token值无效.' if oauth_token != secret - end - def check_callback_url! request_host = URI.parse(callback_url).host callback_url = OauthEducoder.config[:callback_url_host] From 86bbefd0363b1987a30d139b2fba52d60abb95bf Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:08:59 +0800 Subject: [PATCH 32/47] FIX bug --- app/forms/oauth_educoder_form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/forms/oauth_educoder_form.rb b/app/forms/oauth_educoder_form.rb index 2a9cf32e9..258394c33 100644 --- a/app/forms/oauth_educoder_form.rb +++ b/app/forms/oauth_educoder_form.rb @@ -8,7 +8,7 @@ class OauthEducoderForm validates :callback_url, presence: true validate :check_callback_url! - valitate :check_auth! + validate :check_auth! def check_auth! secret = OauthEducoder.config[:access_key_secret] From e11a32e113dfb91d6daa5893418563a9a93180ae Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:17:28 +0800 Subject: [PATCH 33/47] FIX include jquery --- app/controllers/oauth_controller.rb | 4 ++-- app/views/layouts/oauth_register.html.erb | 1 + app/views/oauth/register.html.erb | 20 ++++++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb index f9c526fac..2d7bb1cc9 100644 --- a/app/controllers/oauth_controller.rb +++ b/app/controllers/oauth_controller.rb @@ -60,8 +60,8 @@ class OauthController < ApplicationController def auto_register login = params[:login] - email = params[:email] - password = params[:login] + email = params[:mail] + password = params[:password] platform = params[:plathform] || 'forge' result = autologin_register(login, email, password, platform) diff --git a/app/views/layouts/oauth_register.html.erb b/app/views/layouts/oauth_register.html.erb index cb1e4ef47..1e43ddbea 100644 --- a/app/views/layouts/oauth_register.html.erb +++ b/app/views/layouts/oauth_register.html.erb @@ -2,6 +2,7 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> + <%= javascript_include_tag '/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3', '': '' %> <%= stylesheet_link_tag '/stylesheets/css/oauth', '', :media => 'all' %> diff --git a/app/views/oauth/register.html.erb b/app/views/oauth/register.html.erb index 4b053b542..77bfe749a 100644 --- a/app/views/oauth/register.html.erb +++ b/app/views/oauth/register.html.erb @@ -1,11 +1,12 @@

完善信息,进入比赛

- <%= form_tag(oauth_auto_register_path, method: :post, class: 'form-inline search-form flex-1') do %> + + <%= form_tag('', method: :post, id: 'oauth_form', class: 'form-inline search-form flex-1', remote: true) do %> <%= hidden_field_tag 'callback_url', params[:callback_url] %>
用户名: - <%= text_field_tag :mail, params[:login], placeholder: '请输入用户名', disabled: true, id: 'login' %> + <%= text_field_tag :login, params[:login], placeholder: '请输入用户名', readonly: true, id: 'login' %>

@@ -19,27 +20,25 @@

- +
<% end %>
From 04ae2da5bc4f54eefc76c3bcb3832c91852e0f1b Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:31:46 +0800 Subject: [PATCH 34/47] FIX config --- app/libs/oauth_educoder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/libs/oauth_educoder.rb b/app/libs/oauth_educoder.rb index 4e700d76d..2d18b03a5 100644 --- a/app/libs/oauth_educoder.rb +++ b/app/libs/oauth_educoder.rb @@ -5,7 +5,7 @@ module OauthEducoder begin config = Rails.application.config_for(:configuration).symbolize_keys! - educoder_config = config[:oauth][:educoder].symbolize_keys! + educoder_config = config[:oauth_educoder].symbolize_keys! raise 'oauth educoder config missing' if educoder_config.blank? rescue => ex raise ex if Rails.env.production? From bf5937fc8587f5572496fbc9b7f5388077d6277b Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:43:39 +0800 Subject: [PATCH 35/47] FIX logger --- app/forms/oauth_educoder_form.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/forms/oauth_educoder_form.rb b/app/forms/oauth_educoder_form.rb index 258394c33..291d08b65 100644 --- a/app/forms/oauth_educoder_form.rb +++ b/app/forms/oauth_educoder_form.rb @@ -12,10 +12,14 @@ class OauthEducoderForm def check_auth! secret = OauthEducoder.config[:access_key_secret] - + Rails.logger.info "==== secret: #{secret}" before_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60-1}" now_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60}" + Rails.logger.info "==== before_raw_pay_load: #{before_raw_pay_load}" + Rails.logger.info "==== now_raw_pay_load: #{now_raw_pay_load}" + Rails.logger.info "==== token: #{token}" + if token != Digest::SHA1.hexdigest(now_raw_pay_load) || token != Digest::SHA1.hexdigest(before_raw_pay_load) raise '你的请求无效值无效.' end From 93dabf9f062f484c2a905c2a3adf5689685a46a1 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:49:33 +0800 Subject: [PATCH 36/47] FIX --- app/forms/oauth_educoder_form.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/forms/oauth_educoder_form.rb b/app/forms/oauth_educoder_form.rb index 291d08b65..92910b1b3 100644 --- a/app/forms/oauth_educoder_form.rb +++ b/app/forms/oauth_educoder_form.rb @@ -11,16 +11,17 @@ class OauthEducoderForm validate :check_auth! def check_auth! + Rails.logger.info "====login: #{login} ====token: #{token} ==== callback_url: #{callback_url}" secret = OauthEducoder.config[:access_key_secret] Rails.logger.info "==== secret: #{secret}" - before_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60-1}" - now_raw_pay_load = "#{login}#{secret}#{Time.now.to_i/60}" + before_raw_pay_load = Digest::SHA1.hexdigest("#{login}#{secret}#{Time.now.to_i/60-1}") + now_raw_pay_load = Digest::SHA1.hexdigest("#{login}#{secret}#{Time.now.to_i/60}") Rails.logger.info "==== before_raw_pay_load: #{before_raw_pay_load}" Rails.logger.info "==== now_raw_pay_load: #{now_raw_pay_load}" Rails.logger.info "==== token: #{token}" - if token != Digest::SHA1.hexdigest(now_raw_pay_load) || token != Digest::SHA1.hexdigest(before_raw_pay_load) + if token != now_raw_pay_load || token != before_raw_pay_load raise '你的请求无效值无效.' end end From c38b073715918789f59d0117fcade5e0eb9d1549 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:53:11 +0800 Subject: [PATCH 37/47] FIX validat bug --- app/forms/oauth_educoder_form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/forms/oauth_educoder_form.rb b/app/forms/oauth_educoder_form.rb index 92910b1b3..c7644f5fa 100644 --- a/app/forms/oauth_educoder_form.rb +++ b/app/forms/oauth_educoder_form.rb @@ -21,7 +21,7 @@ class OauthEducoderForm Rails.logger.info "==== now_raw_pay_load: #{now_raw_pay_load}" Rails.logger.info "==== token: #{token}" - if token != now_raw_pay_load || token != before_raw_pay_load + if token != now_raw_pay_load && token != before_raw_pay_load raise '你的请求无效值无效.' end end From 675c3809676a8042f186d517023a0a6928a9255e Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 21:56:16 +0800 Subject: [PATCH 38/47] FIX --- app/models/open_users/educoder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/open_users/educoder.rb b/app/models/open_users/educoder.rb index 8993fe59f..9277b2a1d 100644 --- a/app/models/open_users/educoder.rb +++ b/app/models/open_users/educoder.rb @@ -1,4 +1,4 @@ -class OpenUsers::EduCoder < OpenUser +class OpenUsers::Educoder < OpenUser def nickname extra&.[]('nickname') end From 17183d8769fd72f2996aba462c56e95fd3dc7cd2 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 22:00:37 +0800 Subject: [PATCH 39/47] FIX --- app/controllers/oauth/educoder_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb index 2d1bef189..23b8f19b8 100644 --- a/app/controllers/oauth/educoder_controller.rb +++ b/app/controllers/oauth/educoder_controller.rb @@ -7,7 +7,7 @@ class Oauth::EducoderController < Oauth::BaseController ::OauthEducoderForm.new({login: login, token: token, callback_url: callback_url}).validate! - open_user= OpenUser::Educoder.find_by(uid: login) + open_user= OpenUsers::Educoder.find_by(uid: login) if open_user.present? && open_user.user.present? && open_user.user.email_bind? # 存在说明绑定了,验证信息是否齐全, From 17da5fad38b9b3b42aaea411f3957e3bd0463534 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 22:05:22 +0800 Subject: [PATCH 40/47] ADD log --- app/controllers/oauth/educoder_controller.rb | 2 +- app/controllers/oauth_controller.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb index 23b8f19b8..317fe03db 100644 --- a/app/controllers/oauth/educoder_controller.rb +++ b/app/controllers/oauth/educoder_controller.rb @@ -21,7 +21,7 @@ class Oauth::EducoderController < Oauth::BaseController # 未存在需要进行绑定 if current_user.blank? || !current_user.logged? # forge平台未登录 - redirect_to oauth_register_path(user_id: login, callback_url: callback_url) + redirect_to oauth_register_path(login: login, callback_url: callback_url) else # forge平台已登录 OpenUsers::Educoder.create!(user: current_user, uid: login) diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb index 2d7bb1cc9..8b18b3069 100644 --- a/app/controllers/oauth_controller.rb +++ b/app/controllers/oauth_controller.rb @@ -55,6 +55,8 @@ class OauthController < ApplicationController end def register + logger.info "=====auto_register=======login: #{params[:login]}" + logger.info "=====auto_register=======callback_url: #{params[:callback_url]}" # redirect_to params[:callback_url] end From 775c27a45ba2767bd9b557431ae0b1393b4a9198 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 22:14:59 +0800 Subject: [PATCH 41/47] FIX login bug --- app/controllers/oauth/educoder_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb index 317fe03db..0a1a6c6b3 100644 --- a/app/controllers/oauth/educoder_controller.rb +++ b/app/controllers/oauth/educoder_controller.rb @@ -9,7 +9,7 @@ class Oauth::EducoderController < Oauth::BaseController open_user= OpenUsers::Educoder.find_by(uid: login) - if open_user.present? && open_user.user.present? && open_user.user.email_bind? + if open_user.present? && open_user.user.present? && open_user.user.email_binded?? # 存在说明绑定了,验证信息是否齐全, if current_user != open_user.user logout_user From c4a92fe4a41a89f24cf82989452ea3b11098c5f3 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 22:16:24 +0800 Subject: [PATCH 42/47] FIX login bug --- app/controllers/oauth/educoder_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb index 317fe03db..3b2f65011 100644 --- a/app/controllers/oauth/educoder_controller.rb +++ b/app/controllers/oauth/educoder_controller.rb @@ -9,7 +9,7 @@ class Oauth::EducoderController < Oauth::BaseController open_user= OpenUsers::Educoder.find_by(uid: login) - if open_user.present? && open_user.user.present? && open_user.user.email_bind? + if open_user.present? && open_user.user.present? && open_user.user.email_binded? # 存在说明绑定了,验证信息是否齐全, if current_user != open_user.user logout_user From 0cd790ecac0fd3fde861ce6e316cf34e997f64a2 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 14 Aug 2020 23:24:54 +0800 Subject: [PATCH 43/47] fix js redirect --- app/controllers/oauth/educoder_controller.rb | 14 +++++++++----- app/controllers/oauth_controller.rb | 15 ++++++++++----- app/views/oauth/register.html.erb | 13 +++++++++++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb index 3b2f65011..bc1a0cb6d 100644 --- a/app/controllers/oauth/educoder_controller.rb +++ b/app/controllers/oauth/educoder_controller.rb @@ -10,18 +10,22 @@ class Oauth::EducoderController < Oauth::BaseController open_user= OpenUsers::Educoder.find_by(uid: login) if open_user.present? && open_user.user.present? && open_user.user.email_binded? - # 存在说明绑定了,验证信息是否齐全, if current_user != open_user.user logout_user successful_authentication(open_user.user) end - redirect_to callback_url else - # 未存在需要进行绑定 if current_user.blank? || !current_user.logged? - # forge平台未登录 - redirect_to oauth_register_path(login: login, callback_url: callback_url) + user = User.find_by(login: login) + if user + successful_authentication(user) + OpenUsers::Educoder.create!(user: user, uid: user.login) + + redirect_to callback_url + else + redirect_to oauth_register_path(login: login, callback_url: callback_url) + end else # forge平台已登录 OpenUsers::Educoder.create!(user: current_user, uid: login) diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb index 8b18b3069..7ed65d53c 100644 --- a/app/controllers/oauth_controller.rb +++ b/app/controllers/oauth_controller.rb @@ -55,22 +55,27 @@ class OauthController < ApplicationController end def register - logger.info "=====auto_register=======login: #{params[:login]}" - logger.info "=====auto_register=======callback_url: #{params[:callback_url]}" - # redirect_to params[:callback_url] end def auto_register login = params[:login] email = params[:mail] password = params[:password] + callback_url = params[:callback_url] platform = params[:plathform] || 'forge' result = autologin_register(login, email, password, platform) - + logger.info "[Oauth educoer] =====#{result}" if result[:message].blank? - redirect_to params[:callback_url] + logger.info "[Oauth educoer] ====auto_register success" + user = User.find result[:user][:id] + successful_authentication(user) + OpenUsers::Educoder.create!(user: user, uid: user.login) + + render_ok({callback_url: callback_url}) + # redirect_to callback_url else + logger.info "[Oauth educoer] ====auto_register failed." render :action => "auto_register" end end diff --git a/app/views/oauth/register.html.erb b/app/views/oauth/register.html.erb index 77bfe749a..df3b1eb67 100644 --- a/app/views/oauth/register.html.erb +++ b/app/views/oauth/register.html.erb @@ -52,7 +52,16 @@ $.ajax({ url: "<%= oauth_auto_register_path %>", data: $("#oauth_form").serialize(), - type: 'post' - }) + type: 'post', + dataType: "json", + success: function(data) { + console.log(data) + if (data) { + // data.redirect contains the string URL to redirect to + // window.location.href = "<%#= params[:callback_url] %>"; + window.location.href = data.callback_url; + } + } + }); } From b7b096d6d7813f2c7315451897f19e42ae6f0d11 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Sat, 15 Aug 2020 10:26:30 +0800 Subject: [PATCH 44/47] FIX password length validate --- app/controllers/oauth_controller.rb | 6 +++++- app/views/oauth/register.html.erb | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb index 7ed65d53c..1cdeb4aa3 100644 --- a/app/controllers/oauth_controller.rb +++ b/app/controllers/oauth_controller.rb @@ -62,7 +62,11 @@ class OauthController < ApplicationController email = params[:mail] password = params[:password] callback_url = params[:callback_url] - platform = params[:plathform] || 'forge' + platform = params[:plathform] || 'educoder' + + if User.where(mail: email).exists? + render_error("该邮箱已使用过.") and return + end result = autologin_register(login, email, password, platform) logger.info "[Oauth educoer] =====#{result}" diff --git a/app/views/oauth/register.html.erb b/app/views/oauth/register.html.erb index df3b1eb67..d9b39543c 100644 --- a/app/views/oauth/register.html.erb +++ b/app/views/oauth/register.html.erb @@ -46,6 +46,9 @@ if(!password){ $(".passwordCheck span").html("请输入账号密码"); return; + }else if(password.length < 8){ + $(".passwordCheck span").html("密码最少为8位数"); + return; }else{ $(".passwordCheck span").html(""); } @@ -57,11 +60,19 @@ success: function(data) { console.log(data) if (data) { + if(data.message){ + $(".emailCheck span").html("该邮箱已存在."); + return; + } // data.redirect contains the string URL to redirect to // window.location.href = "<%#= params[:callback_url] %>"; window.location.href = data.callback_url; } - } + }, + error: function (data) { + console.log('ajax error handling',data); + } + }); } From 90f14d2051fd9b2522c6a42dd9b523a1de8e79e1 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Sat, 15 Aug 2020 10:58:56 +0800 Subject: [PATCH 45/47] FIX projects fork users bug --- app/views/projects/fork_users.json.jbuilder | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/projects/fork_users.json.jbuilder b/app/views/projects/fork_users.json.jbuilder index 95e69ca75..435b7e805 100644 --- a/app/views/projects/fork_users.json.jbuilder +++ b/app/views/projects/fork_users.json.jbuilder @@ -1,12 +1,13 @@ json.count @forks_count -json.users do +json.users do json.array! @fork_users.each do |f| - user = f.user + user = f.user fork_project = Project.select(:id,:name).find_by(id: f.fork_project_id) json.id f.fork_project_id + json.identifier fork_project.identifier json.name "#{user.try(:show_real_name)}/#{fork_project.try(:name)}" json.login user.try(:login) json.image_url url_to_avatar(user) json.format_time f.created_at.strftime("%Y-%m-%d") end -end \ No newline at end of file +end From 8b9f0a9f069cda0b8b595b7509d4ee35c1b689d5 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Sat, 15 Aug 2020 10:59:29 +0800 Subject: [PATCH 46/47] FIX projects fork users bug --- app/views/projects/fork_users.json.jbuilder | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/projects/fork_users.json.jbuilder b/app/views/projects/fork_users.json.jbuilder index 95e69ca75..435b7e805 100644 --- a/app/views/projects/fork_users.json.jbuilder +++ b/app/views/projects/fork_users.json.jbuilder @@ -1,12 +1,13 @@ json.count @forks_count -json.users do +json.users do json.array! @fork_users.each do |f| - user = f.user + user = f.user fork_project = Project.select(:id,:name).find_by(id: f.fork_project_id) json.id f.fork_project_id + json.identifier fork_project.identifier json.name "#{user.try(:show_real_name)}/#{fork_project.try(:name)}" json.login user.try(:login) json.image_url url_to_avatar(user) json.format_time f.created_at.strftime("%Y-%m-%d") end -end \ No newline at end of file +end From ec43c03f0873a87d213dc91cacba45b935eaef99 Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Sat, 15 Aug 2020 11:05:20 +0800 Subject: [PATCH 47/47] FIX bug --- app/views/projects/fork_users.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/fork_users.json.jbuilder b/app/views/projects/fork_users.json.jbuilder index 435b7e805..3feb31edb 100644 --- a/app/views/projects/fork_users.json.jbuilder +++ b/app/views/projects/fork_users.json.jbuilder @@ -2,7 +2,7 @@ json.count @forks_count json.users do json.array! @fork_users.each do |f| user = f.user - fork_project = Project.select(:id,:name).find_by(id: f.fork_project_id) + fork_project = Project.select(:id,:name, :identifier).find_by(id: f.fork_project_id) json.id f.fork_project_id json.identifier fork_project.identifier json.name "#{user.try(:show_real_name)}/#{fork_project.try(:name)}"