| @@ -0,0 +1,26 @@ | |||
| class Projects::AppliedTransferProjectsController < Projects::BaseController | |||
| before_action :check_auth | |||
| def organizations | |||
| @organizations = Organization.includes(:organization_extension).joins(team_users: :team).where(team_users: {user_id: current_user.id}, teams: {authorize: %w(admin owner)}) | |||
| end | |||
| def create | |||
| @applied_transfer_project = Projects::ApplyTransferService.call(current_user, @project, params) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| def cancel | |||
| @applied_transfer_project = Projects::CancelTransferService.call(current_user, @project) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| private | |||
| def check_auth | |||
| return render_forbidden unless current_user.admin? ||@project.owner?(current_user) | |||
| end | |||
| end | |||
| @@ -0,0 +1,18 @@ | |||
| class Users::AppliedMessagesController < Users::BaseController | |||
| before_action :check_auth | |||
| after_action :view_messages, only: [:index] | |||
| def index | |||
| @applied_messages = @_observed_user.applied_messages.order(viewed: :asc, created_at: :desc) | |||
| @applied_messages = paginate @applied_messages | |||
| end | |||
| private | |||
| def check_auth | |||
| return render_forbidden unless observed_logged_user? | |||
| end | |||
| def view_messages | |||
| @applied_messages.update_all(viewed: 'viewed') | |||
| end | |||
| end | |||
| @@ -0,0 +1,41 @@ | |||
| class Users::AppliedTransferProjectsController < Users::BaseController | |||
| before_action :check_auth | |||
| before_action :find_applied_transfer_project, except: [:index] | |||
| before_action :find_project, except: [:index] | |||
| def index | |||
| user_collection_sql = AppliedTransferProject.where(owner_id: @_observed_user.id).to_sql | |||
| org_collection_sql = AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @_observed_user.id}, teams: {authorize: %w(admin owner)} )).to_sql | |||
| @applied_transfer_projects = AppliedTransferProject.from("( #{ user_collection_sql } UNION #{ org_collection_sql } ) AS applied_transfer_projects") | |||
| @applied_transfer_projects = paginate @applied_transfer_projects.order("created_at desc") | |||
| end | |||
| # 接受迁移 | |||
| def accept | |||
| @applied_transfer_project = Projects::AcceptTransferService.call(current_user, @project) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| # 拒绝迁移 | |||
| def refuse | |||
| @applied_transfer_project = Projects::RefuseTransferService.call(current_user, @project) | |||
| rescue Exception => e | |||
| uid_logger_error(e.message) | |||
| tip_exception(e.message) | |||
| end | |||
| private | |||
| def check_auth | |||
| return render_forbidden unless observed_logged_user? | |||
| end | |||
| def find_applied_transfer_project | |||
| @applied_transfer_project = AppliedTransferProject.find_by_id params[:id] | |||
| end | |||
| def find_project | |||
| @project = @applied_transfer_project.project | |||
| end | |||
| end | |||
| @@ -27,11 +27,20 @@ class UsersController < ApplicationController | |||
| def show | |||
| #待办事项,现在未做 | |||
| @undo_events = 0 | |||
| if User.current.login == @user.login | |||
| @waiting_applied_messages = @user.applied_messages.waiting | |||
| @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common | |||
| @undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size | |||
| else | |||
| @waiting_applied_messages = AppliedMessage.none | |||
| @common_applied_transfer_projects = AppliedTransferProject.none | |||
| @undo_events = 0 | |||
| end | |||
| #用户的组织数量 | |||
| # @user_composes_count = @user.composes.size | |||
| @user_composes_count = 0 | |||
| @user_org_count = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)).size + @user.organizations.with_visibility("privacy").joins(:organization_users).where(organization_users: {user_id: current_user.id}).size : @user.organizations.with_visibility("common").size | |||
| user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common") | |||
| @user_org_count = user_organizations.size | |||
| user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? @user.projects : @user.projects.visible | |||
| @projects_common_count = user_projects.common.size | |||
| @projects_mirrior_count = user_projects.mirror.size | |||
| @@ -526,3 +526,241 @@ await octokit.request('POST /api/jaser/jasder_test/forks.json') | |||
| "identifier": "newadm" | |||
| } | |||
| ``` | |||
| ## 用户管理的组织列表 | |||
| 用户管理的组织列表 | |||
| > 示例: | |||
| ```shell | |||
| curl -X GET \ | |||
| http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizations.json | jq | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('GET /api/:owner/:repo/applied_transfer_projects/organizations') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `GET api/:owner/:repo/applied_transfer_projects/organizations` | |||
| ### 请求参数 | |||
| 参数 | 必选 | 默认 | 类型 | 字段说明 | |||
| --------- | ------- | ------- | -------- | ---------- | |||
| owner |是| |string |用户登录名 | |||
| repo |是| |string |项目标识identifier | |||
| ### 返回字段说明 | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| name |string|组织标识 | |||
| nickname |string|组织名称 | |||
| description|string|组织描述 | |||
| avatar_url|string|组织头像 | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "total_count": 3, | |||
| "organizations": [ | |||
| { | |||
| "id": 9, | |||
| "name": "ceshi_org", | |||
| "nickname": "测试组织", | |||
| "description": "测试组织", | |||
| "avatar_url": "images/avatars/Organization/9?t=1612706073" | |||
| }, | |||
| { | |||
| "id": 51, | |||
| "name": "ceshi", | |||
| "nickname": "测试组织哈哈哈", | |||
| "description": "23212312", | |||
| "avatar_url": "images/avatars/Organization/51?t=1618800723" | |||
| }, | |||
| { | |||
| "id": 52, | |||
| "name": "ceshi1", | |||
| "nickname": "身份卡手动阀", | |||
| "description": "1231手动阀是的", | |||
| "avatar_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| ] | |||
| } | |||
| ``` | |||
| ## 迁移项目 | |||
| 迁移项目,edit接口is_transfering为true表示正在迁移 | |||
| > 示例: | |||
| ```shell | |||
| curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects.json | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('POST /api/:owner/:repo/applied_transfer_projects.json') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `POST /api/:owner/:repo/applied_transfer_projects.json` | |||
| ### 请求参数 | |||
| 参数 | 必选 | 默认 | 类型 | 字段说明 | |||
| --------- | ------- | ------- | -------- | ---------- | |||
| |owner |是| |string |用户登录名 | | |||
| |repo |是| |string |项目标识identifier | | |||
| |owner_name|是| |string |迁移对象标识 | | |||
| ### 返回字段说明 | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |id |int |项目id | | |||
| |status |string |项目迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| | |||
| |time_ago |string |项目迁移创建的时间 | | |||
| |project.id |int |迁移项目的id | | |||
| |project.identifier |string |迁移项目的标识 | | |||
| |project.name |string |迁移项目的名称 | | |||
| |project.description |string |迁移项目的描述 | | |||
| |project.is_public |bool |迁移项目是否公开 | | |||
| |project.owner.id |bool |迁移项目拥有者id | | |||
| |project.owner.type |string |迁移项目拥有者类型 | | |||
| |project.owner.name |string |迁移项目拥有者昵称 | | |||
| |project.owner.login |string |迁移项目拥有者标识 | | |||
| |project.owner.image_url |string |迁移项目拥有者头像 | | |||
| |user.id |int |迁移创建者的id | | |||
| |user.type |string |迁移创建者的类型 | | |||
| |user.name |string |迁移创建者的名称 | | |||
| |user.login |string |迁移创建者的标识 | | |||
| |user.image_url |string |迁移创建者头像 | | |||
| |owner.id |int |迁移接受者的id | | |||
| |owner.type |string |迁移接受者的类型 | | |||
| |owner.name |string |迁移接受者的名称 | | |||
| |owner.login |string |迁移接受者的标识 | | |||
| |owner.image_url |string |迁移接受者头像 | | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "project": { | |||
| "id": 86, | |||
| "identifier": "ceshi_repo1", | |||
| "name": "测试项目啊1", | |||
| "description": "二十多", | |||
| "is_public": true, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| }, | |||
| "user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "owner": { | |||
| "id": 9, | |||
| "type": "Organization", | |||
| "name": "测试组织", | |||
| "login": "ceshi_org", | |||
| "image_url": "images/avatars/Organization/9?t=1612706073" | |||
| }, | |||
| "id": 4, | |||
| "status": "common", | |||
| "created_at": "2021-04-26 09:54", | |||
| "time_ago": "1分钟前" | |||
| } | |||
| ``` | |||
| ## 取消迁移项目 | |||
| 迁移项目,edit接口is_transfering为true表示正在迁移 | |||
| > 示例: | |||
| ```shell | |||
| curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/cancel.json | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.json') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `POST /api/:owner/:repo/applied_transfer_projects/cancel.json` | |||
| ### 请求参数 | |||
| 参数 | 必选 | 默认 | 类型 | 字段说明 | |||
| --------- | ------- | ------- | -------- | ---------- | |||
| |owner |是| |string |用户登录名 | | |||
| |repo |是| |string |项目标识identifier | | |||
| ### 返回字段说明 | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |id |int |迁移id | | |||
| |status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| | |||
| |time_ago |string |迁移创建的时间 | | |||
| |project.id |int |迁移项目的id | | |||
| |project.identifier |string |迁移项目的标识 | | |||
| |project.name |string |迁移项目的名称 | | |||
| |project.description |string |迁移项目的描述 | | |||
| |project.is_public |bool |迁移项目是否公开 | | |||
| |project.owner.id |bool |迁移项目拥有者id | | |||
| |project.owner.type |string |迁移项目拥有者类型 | | |||
| |project.owner.name |string |迁移项目拥有者昵称 | | |||
| |project.owner.login |string |迁移项目拥有者标识 | | |||
| |project.owner.image_url |string |迁移项目拥有者头像 | | |||
| |user.id |int |迁移创建者的id | | |||
| |user.type |string |迁移创建者的类型 | | |||
| |user.name |string |迁移创建者的名称 | | |||
| |user.login |string |迁移创建者的标识 | | |||
| |user.image_url |string |迁移创建者头像 | | |||
| |owner.id |int |迁移接受者的id | | |||
| |owner.type |string |迁移接受者的类型 | | |||
| |owner.name |string |迁移接受者的名称 | | |||
| |owner.login |string |迁移接受者的标识 | | |||
| |owner.image_url |string |迁移接受者头像 | | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "project": { | |||
| "id": 86, | |||
| "identifier": "ceshi_repo1", | |||
| "name": "测试项目啊1", | |||
| "description": "二十多", | |||
| "is_public": true, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| }, | |||
| "user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "owner": { | |||
| "id": 9, | |||
| "type": "Organization", | |||
| "name": "测试组织", | |||
| "login": "ceshi_org", | |||
| "image_url": "images/avatars/Organization/9?t=1612706073" | |||
| }, | |||
| "id": 4, | |||
| "status": "common", | |||
| "created_at": "2021-04-26 09:54", | |||
| "time_ago": "1分钟前" | |||
| } | |||
| ``` | |||
| @@ -1,3 +1,9 @@ | |||
| <!-- | |||
| * @Date: 2021-03-01 10:35:21 | |||
| * @LastEditors: viletyy | |||
| * @LastEditTime: 2021-04-26 10:47:30 | |||
| * @FilePath: /forgeplus/app/docs/slate/source/includes/_users.md | |||
| --> | |||
| # Users | |||
| ## 获取当前登陆用户信息 | |||
| @@ -40,3 +46,390 @@ await octokit.request('GET /api/users/me.json') | |||
| <aside class="success"> | |||
| Success Data. | |||
| </aside> | |||
| ## 待办事项-用户通知信息 | |||
| 待办事项-用户通知信息 | |||
| > 示例: | |||
| ```shell | |||
| curl -X GET http://localhost:3000/api/users/yystopf/applied_messages.json | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('GET /api/users/:login/applied_messages.json') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `GET /api/users/:login/applied_messages.json` | |||
| ### 请求字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |login |string |用户标识 | | |||
| ### 返回字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |applied |object |通知主体 | | |||
| |applied.id |int |通知主体的迁移id | | |||
| |applied.status |string |通知主体的迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| | |||
| |applied.time_ago |string |通知主体的迁移创建的时间 | | |||
| |applied.project.id |int |通知主体的迁移项目的id | | |||
| |applied.project.identifier |string |通知主体的迁移项目的标识 | | |||
| |applied.project.name |string |通知主体的迁移项目的名称 | | |||
| |applied.project.description |string |通知主体的迁移项目的描述 | | |||
| |applied.project.is_public |bool |通知主体的迁移项目是否公开 | | |||
| |applied.project.owner.id |bool |通知主体的迁移项目拥有者id | | |||
| |applied.project.owner.type |string |通知主体的迁移项目拥有者类型 | | |||
| |applied.project.owner.name |string |通知主体的迁移项目拥有者昵称 | | |||
| |applied.project.owner.login |string |通知主体的迁移项目拥有者标识 | | |||
| |applied.project.owner.image_url |string |通知主体的迁移项目拥有者头像 | | |||
| |applied.user.id |int |通知主体的迁移创建者的id | | |||
| |applied.user.type |string |通知主体的迁移创建者的类型 | | |||
| |applied.user.name |string |通知主体的迁移创建者的名称 | | |||
| |applied.user.login |string |通知主体的迁移创建者的标识 | | |||
| |applied.user.image_url |string |通知主体的迁移创建者头像 | | |||
| |applied.owner.id |int |通知主体的迁移接受者的id | | |||
| |applied.owner.type |string |通知主体的迁移接受者的类型 | | |||
| |applied.owner.name |string |通知主体的迁移接受者的名称 | | |||
| |applied.owner.login |string |通知主体的迁移接受者的标识 | | |||
| |applied.owner.image_url |string |通知主体的迁移接受者头像 | | |||
| |applied_type |string |通知类型 | | |||
| |name |string | 通知内容 | | |||
| |viewed |string|是否已读,waiting:未读,viewed:已读| | |||
| |status |string|通知状态, canceled:已取消,common: 正常,successed:成功,failure:失败| | |||
| |time_ago |string|通知时间| | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "total_count": 5, | |||
| "applied_messages": [ | |||
| { | |||
| "applied": { | |||
| "project": { | |||
| "id": 86, | |||
| "identifier": "ceshi_repo1", | |||
| "name": "测试项目啊1", | |||
| "description": "二十多", | |||
| "is_public": true, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| }, | |||
| "user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "owner": { | |||
| "id": 9, | |||
| "type": "Organization", | |||
| "name": "测试组织", | |||
| "login": "ceshi_org", | |||
| "image_url": "images/avatars/Organization/9?t=1612706073" | |||
| }, | |||
| "id": 4, | |||
| "status": "common", | |||
| "created_at": "2021-04-26 09:54", | |||
| "time_ago": "35分钟前" | |||
| }, | |||
| "applied_user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "applied_type": "AppliedTransferProject", | |||
| "name": "正在将【测试项目啊1】仓库转移给【测试组织】", | |||
| "viewed": "viewed", | |||
| "status": "common", | |||
| "created_at": "2021-04-26 09:54", | |||
| "time_ago": "35分钟前" | |||
| }, | |||
| ... | |||
| ] | |||
| } | |||
| ``` | |||
| ## 待办事项-接受仓库 | |||
| 待办事项-接受仓库 | |||
| > 示例: | |||
| ```shell | |||
| curl -X GET http://localhost:3000/api/users/yystopf/applied_transfer_projects.json | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('GET /api/users/:login/applied_transfer_projects.json') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `GET /api/users/:login/applied_transfer_projects.json` | |||
| ### 请求字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |login |string |用户标识 | | |||
| ### 返回字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |id |int |迁移id | | |||
| |status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| | |||
| |time_ago |string |迁移创建的时间 | | |||
| |project.id |int |迁移项目的id | | |||
| |project.identifier |string |迁移项目的标识 | | |||
| |project.name |string |迁移项目的名称 | | |||
| |project.description |string |迁移项目的描述 | | |||
| |project.is_public |bool |迁移项目是否公开 | | |||
| |project.owner.id |bool |迁移项目拥有者id | | |||
| |project.owner.type |string |迁移项目拥有者类型 | | |||
| |project.owner.name |string |迁移项目拥有者昵称 | | |||
| |project.owner.login |string |迁移项目拥有者标识 | | |||
| |project.owner.image_url |string |迁移项目拥有者头像 | | |||
| |user.id |int |迁移创建者的id | | |||
| |user.type |string |迁移创建者的类型 | | |||
| |user.name |string |迁移创建者的名称 | | |||
| |user.login |string |迁移创建者的标识 | | |||
| |user.image_url |string |迁移创建者头像 | | |||
| |owner.id |int |迁移接受者的id | | |||
| |owner.type |string |迁移接受者的类型 | | |||
| |owner.name |string |迁移接受者的名称 | | |||
| |owner.login |string |迁移接受者的标识 | | |||
| |owner.image_url |string |迁移接受者头像 | | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "total_count": 4, | |||
| "applied_transfer_projects": [ | |||
| { | |||
| "project": { | |||
| "id": 86, | |||
| "identifier": "ceshi_repo1", | |||
| "name": "测试项目啊1", | |||
| "description": "二十多", | |||
| "is_public": true, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| }, | |||
| "user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| }, | |||
| "id": 1, | |||
| "status": "canceled", | |||
| "created_at": "2021-04-25 18:06", | |||
| "time_ago": "16小时前" | |||
| }, | |||
| ... | |||
| ] | |||
| } | |||
| ``` | |||
| ## 用户接受迁移 | |||
| 用户接受迁移 | |||
| > 示例: | |||
| ```shell | |||
| curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/accept.json | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/accept.json') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `GET /api/users/:login/applied_transfer_projects/:id/accept.json` | |||
| ### 请求字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |login |string |用户标识 | | |||
| |id |int |迁移id | | |||
| ### 返回字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |id |int |迁移id | | |||
| |status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| | |||
| |time_ago |string |迁移创建的时间 | | |||
| |project.id |int |迁移项目的id | | |||
| |project.identifier |string |迁移项目的标识 | | |||
| |project.name |string |迁移项目的名称 | | |||
| |project.description |string |迁移项目的描述 | | |||
| |project.is_public |bool |迁移项目是否公开 | | |||
| |project.owner.id |bool |迁移项目拥有者id | | |||
| |project.owner.type |string |迁移项目拥有者类型 | | |||
| |project.owner.name |string |迁移项目拥有者昵称 | | |||
| |project.owner.login |string |迁移项目拥有者标识 | | |||
| |project.owner.image_url |string |迁移项目拥有者头像 | | |||
| |user.id |int |迁移创建者的id | | |||
| |user.type |string |迁移创建者的类型 | | |||
| |user.name |string |迁移创建者的名称 | | |||
| |user.login |string |迁移创建者的标识 | | |||
| |user.image_url |string |迁移创建者头像 | | |||
| |owner.id |int |迁移接受者的id | | |||
| |owner.type |string |迁移接受者的类型 | | |||
| |owner.name |string |迁移接受者的名称 | | |||
| |owner.login |string |迁移接受者的标识 | | |||
| |owner.image_url |string |迁移接受者头像 | | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "project": { | |||
| "id": 86, | |||
| "identifier": "ceshi_repo1", | |||
| "name": "测试项目啊1", | |||
| "description": "二十多", | |||
| "is_public": true, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| }, | |||
| "user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| }, | |||
| "id": 1, | |||
| "status": "canceled", | |||
| "created_at": "2021-04-25 18:06", | |||
| "time_ago": "16小时前" | |||
| } | |||
| ``` | |||
| ## 用户拒绝迁移 | |||
| 用户拒绝迁移 | |||
| > 示例: | |||
| ```shell | |||
| curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/refuse.json | |||
| ``` | |||
| ```javascript | |||
| await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/refuse.json') | |||
| ``` | |||
| ### HTTP 请求 | |||
| `GET /api/users/:login/applied_transfer_projects/:id/refuse.json` | |||
| ### 请求字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |login |string |用户标识 | | |||
| |id |int |迁移id | | |||
| ### 返回字段说明: | |||
| 参数 | 类型 | 字段说明 | |||
| --------- | ----------- | ----------- | |||
| |id |int |迁移id | | |||
| |status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| | |||
| |time_ago |string |迁移创建的时间 | | |||
| |project.id |int |迁移项目的id | | |||
| |project.identifier |string |迁移项目的标识 | | |||
| |project.name |string |迁移项目的名称 | | |||
| |project.description |string |迁移项目的描述 | | |||
| |project.is_public |bool |迁移项目是否公开 | | |||
| |project.owner.id |bool |迁移项目拥有者id | | |||
| |project.owner.type |string |迁移项目拥有者类型 | | |||
| |project.owner.name |string |迁移项目拥有者昵称 | | |||
| |project.owner.login |string |迁移项目拥有者标识 | | |||
| |project.owner.image_url |string |迁移项目拥有者头像 | | |||
| |user.id |int |迁移创建者的id | | |||
| |user.type |string |迁移创建者的类型 | | |||
| |user.name |string |迁移创建者的名称 | | |||
| |user.login |string |迁移创建者的标识 | | |||
| |user.image_url |string |迁移创建者头像 | | |||
| |owner.id |int |迁移接受者的id | | |||
| |owner.type |string |迁移接受者的类型 | | |||
| |owner.name |string |迁移接受者的名称 | | |||
| |owner.login |string |迁移接受者的标识 | | |||
| |owner.image_url |string |迁移接受者头像 | | |||
| > 返回的JSON示例: | |||
| ```json | |||
| { | |||
| "project": { | |||
| "id": 86, | |||
| "identifier": "ceshi_repo1", | |||
| "name": "测试项目啊1", | |||
| "description": "二十多", | |||
| "is_public": true, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| } | |||
| }, | |||
| "user": { | |||
| "id": 6, | |||
| "type": "User", | |||
| "name": "yystopf", | |||
| "login": "yystopf", | |||
| "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" | |||
| }, | |||
| "owner": { | |||
| "id": 52, | |||
| "type": "Organization", | |||
| "name": "身份卡手动阀", | |||
| "login": "ceshi1", | |||
| "image_url": "images/avatars/Organization/52?t=1618805056" | |||
| }, | |||
| "id": 1, | |||
| "status": "canceled", | |||
| "created_at": "2021-04-25 18:06", | |||
| "time_ago": "16小时前" | |||
| } | |||
| ``` | |||
| @@ -0,0 +1,47 @@ | |||
| class SendTransferProjectAppliedMessageJob < ApplicationJob | |||
| queue_as :default | |||
| def perform(applied_transfer_project, applied_user, message_status) | |||
| project = applied_transfer_project.project | |||
| owner = project.owner | |||
| return unless project.present? | |||
| return unless owner.present? | |||
| if owner.is_a?(Organization) | |||
| receivers = project.managers + owner.team_users.joins(:team).where(teams: {authorize: %w(owner admin)}) | |||
| else | |||
| receivers = project.managers | |||
| end | |||
| receivers.each do |rec| | |||
| next if applied_user.id == rec.user_id # 自己不要给自己发通知 | |||
| AppliedMessage.create!(user_id: rec.user_id, | |||
| applied: applied_transfer_project, | |||
| status: message_status, | |||
| name: build_name(project.name, applied_transfer_project&.owner&.real_name, message_status, applied_user&.real_name), | |||
| applied_user_id: applied_user.id, | |||
| project_id: project.id) | |||
| end | |||
| if message_status == 'successed' # 如果转移成功,给转移发起者发通知已转移成功 | |||
| AppliedMessage.find_or_create_by!(user_id: applied_transfer_project.user_id, | |||
| applied: applied_transfer_project, | |||
| status: message_status, | |||
| name: build_name(project.name, applied_transfer_project&.owner&.real_name, message_status), | |||
| applied_user_id: applied_user.id, | |||
| project_id: project.id) | |||
| end | |||
| end | |||
| private | |||
| def build_name(repo_name, owner_name, message_status, applied_name="") | |||
| case message_status | |||
| when 'canceled' | |||
| return "取消转移【#{repo_name}】仓库" | |||
| when 'common' | |||
| return "正在将【#{repo_name}】仓库转移给【#{owner_name}】" | |||
| when 'successed' | |||
| return "【#{repo_name}】仓库成功转移给【#{owner_name}】" | |||
| when 'failure' | |||
| return "拒绝转移【#{repo_name}】仓库" | |||
| end | |||
| "" | |||
| end | |||
| end | |||
| @@ -19,5 +19,10 @@ | |||
| class AppliedMessage < ApplicationRecord | |||
| belongs_to :user | |||
| belongs_to :applied, polymorphic: true | |||
| belongs_to :project | |||
| belongs_to :applied_user, class_name: 'User' | |||
| enum viewed: {waiting: 0, viewed: 1} | |||
| enum status: {canceled: -1, common: 0, successed: 1, failure: 2} # -1 已取消 0 正在操作 1 操作成功 2 操作失败 | |||
| end | |||
| @@ -0,0 +1,28 @@ | |||
| # == Schema Information | |||
| # | |||
| # Table name: applied_transfer_projects | |||
| # | |||
| # id :integer not null, primary key | |||
| # project_id :integer | |||
| # owner_id :integer | |||
| # user_id :integer | |||
| # status :integer default("0") | |||
| # created_at :datetime not null | |||
| # updated_at :datetime not null | |||
| # | |||
| # Indexes | |||
| # | |||
| # index_applied_transfer_projects_on_owner_id (owner_id) | |||
| # index_applied_transfer_projects_on_project_id (project_id) | |||
| # index_applied_transfer_projects_on_user_id (user_id) | |||
| # | |||
| class AppliedTransferProject < ApplicationRecord | |||
| belongs_to :project | |||
| belongs_to :user # 操作者 | |||
| belongs_to :owner # 接收个人或组织 | |||
| has_many :applied_messages, as: :applied, dependent: :destroy | |||
| enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝 | |||
| end | |||
| @@ -66,4 +66,6 @@ class Owner < ApplicationRecord | |||
| has_many :projects, foreign_key: :user_id, dependent: :destroy | |||
| has_many :repositories, foreign_key: :user_id, dependent: :destroy | |||
| has_many :applied_transfer_projects, dependent: :destroy | |||
| end | |||
| @@ -112,6 +112,7 @@ class Project < ApplicationRecord | |||
| has_one :project_detail, dependent: :destroy | |||
| has_many :team_projects, dependent: :destroy | |||
| has_many :project_units, dependent: :destroy | |||
| has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy | |||
| after_save :check_project_members | |||
| scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)} | |||
| @@ -295,4 +296,7 @@ class Project < ApplicationRecord | |||
| update_column(:updated_on, time) | |||
| end | |||
| def is_transfering | |||
| applied_transfer_project&.common? ? true : false | |||
| end | |||
| end | |||
| @@ -148,9 +148,11 @@ class User < Owner | |||
| has_many :trail_auth_apply_actions, -> { where(container_type: 'TrialAuthorization') }, class_name: 'ApplyAction' | |||
| # has_many :attendances | |||
| has_many :applied_messages, dependent: :destroy | |||
| has_many :operate_applied_messages, class_name: 'AppliedMessage', dependent: :destroy | |||
| # 项目 | |||
| has_many :applied_projects, dependent: :destroy | |||
| has_many :operate_applied_transfer_projects, class_name: 'AppliedTransferProject', dependent: :destroy | |||
| # 教学案例 | |||
| # has_many :libraries, dependent: :destroy | |||
| @@ -0,0 +1,49 @@ | |||
| class Projects::AcceptTransferService < ApplicationService | |||
| attr_accessor :applied_transfer_project, :owner | |||
| attr_reader :user, :project | |||
| def initialize(user, project) | |||
| @user = user | |||
| @project = project | |||
| @applied_transfer_project = project.applied_transfer_project | |||
| @owner = @applied_transfer_project.owner | |||
| end | |||
| def call | |||
| Rails.logger.info("###### Project accept_transfer_service begin ######") | |||
| ActiveRecord::Base.transaction do | |||
| validate! | |||
| update_apply | |||
| operate_project | |||
| send_apply_message | |||
| end | |||
| Rails.logger.info("##### Project accept_transfer_service end ######") | |||
| return @applied_transfer_project | |||
| end | |||
| private | |||
| def validate! | |||
| raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering | |||
| raise Error, '未拥有接受转移权限' unless is_permit_operator | |||
| end | |||
| def is_permit_operator | |||
| return true if @user == @owner | |||
| return @owner.is_a?(Organization) && @owner.is_admin?(@user) | |||
| end | |||
| def update_apply | |||
| @applied_transfer_project.update!(status: 'accepted') | |||
| end | |||
| def operate_project | |||
| @project = Projects::TransferService.call(@project, @owner) | |||
| end | |||
| def send_apply_message | |||
| SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'successed') | |||
| end | |||
| end | |||
| @@ -0,0 +1,43 @@ | |||
| class Projects::ApplyTransferService < ApplicationService | |||
| attr_accessor :owner, :applied_transfer_project | |||
| attr_reader :user, :project, :params | |||
| def initialize(user, project, params) | |||
| @user = user | |||
| @project = project | |||
| @params = params | |||
| @owner = Owner.find_by(login: params[:owner_name]) | |||
| end | |||
| def call | |||
| Rails.logger.info("###### Project apply_transfer_service begin ######") | |||
| validate! | |||
| create_apply | |||
| send_apply_message | |||
| Rails.logger.info("###### Project apply_transfer_service end ######") | |||
| return @applied_transfer_project | |||
| end | |||
| private | |||
| def validate! | |||
| raise Error, '仓库标识不正确' if @project.identifier != params[:identifier] | |||
| raise Error, '该仓库正在迁移' if @project.is_transfering | |||
| raise Error, '新拥有者不存在' unless @owner.present? | |||
| raise Error, '新拥有者已经存在同名仓库!' if Project.where(user_id: @owner.id, identifier: params[:identifier]).present? | |||
| raise Error, '未拥有转移权限' unless is_permit_owner | |||
| end | |||
| def is_permit_owner | |||
| return true unless @owner.is_a?(Organization) | |||
| return @owner.is_owner?(@user) | |||
| end | |||
| def create_apply | |||
| @applied_transfer_project = AppliedTransferProject.create!(user_id: user.id, project_id: project.id, owner_id: @owner.id) | |||
| end | |||
| def send_apply_message | |||
| SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'common') | |||
| end | |||
| end | |||
| @@ -0,0 +1,33 @@ | |||
| class Projects::CancelTransferService < ApplicationService | |||
| attr_accessor :applied_transfer_project | |||
| attr_reader :user, :project | |||
| def initialize(user, project) | |||
| @user = user | |||
| @project = project | |||
| @applied_transfer_project = project.applied_transfer_project | |||
| end | |||
| def call | |||
| Rails.logger.info("###### Project cancel_transfer_service begin ######") | |||
| validate! | |||
| update_apply | |||
| send_apply_message | |||
| Rails.logger.info("###### Project cancel_transfer_service end ######") | |||
| return @applied_transfer_project | |||
| end | |||
| private | |||
| def validate! | |||
| raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering | |||
| end | |||
| def update_apply | |||
| @applied_transfer_project.update!(status: 'canceled') | |||
| end | |||
| def send_apply_message | |||
| SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'canceled') | |||
| end | |||
| end | |||
| @@ -0,0 +1,40 @@ | |||
| class Projects::RefuseTransferService < ApplicationService | |||
| attr_accessor :applied_transfer_project, :owner | |||
| attr_reader :user, :project | |||
| def initialize(user, project) | |||
| @user = user | |||
| @project = project | |||
| @applied_transfer_project = project.applied_transfer_project | |||
| @owner = @applied_transfer_project.owner | |||
| end | |||
| def call | |||
| Rails.logger.info("###### Project refuse_transfer_service begin ######") | |||
| validate! | |||
| update_apply | |||
| send_apply_message | |||
| Rails.logger.info("###### Project refuse_transfer_service end ######") | |||
| return @applied_transfer_project | |||
| end | |||
| private | |||
| def validate! | |||
| raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering | |||
| raise Error, '未拥有拒绝转移权限' unless is_permit_operator | |||
| end | |||
| def is_permit_operator | |||
| return true if @user == @owner | |||
| return @owner.is_a?(Organization) && @owner.is_admin?(@user) | |||
| end | |||
| def update_apply | |||
| @applied_transfer_project.update!(status: 'refused') | |||
| end | |||
| def send_apply_message | |||
| SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'failure') | |||
| end | |||
| end | |||
| @@ -23,6 +23,7 @@ class Projects::TransferService < ApplicationService | |||
| private | |||
| def update_owner | |||
| project.members.find_by(user_id: owner.id).destroy! if owner.is_a?(User) | |||
| project.update!(user_id: new_owner.id) | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| json.id organization.id | |||
| json.name organization.login | |||
| json.nickname organization.nickname.blank? ? organization.name : organization.nickname | |||
| json.description organization.description | |||
| json.avatar_url url_to_avatar(organization) | |||
| @@ -0,0 +1,21 @@ | |||
| project = object.project | |||
| json.project do | |||
| json.id project.id | |||
| json.identifier project.identifier | |||
| json.name project.name | |||
| json.description project.description | |||
| json.is_public project.is_public | |||
| json.owner do | |||
| json.partial! "/users/user_simple", locals: {user: project.owner} | |||
| end | |||
| end | |||
| json.user do | |||
| json.partial! "/users/user_simple", locals: {user: object.user} | |||
| end | |||
| json.owner do | |||
| json.partial! "/users/user_simple", locals: {user: object.owner} | |||
| end | |||
| json.id object.id | |||
| json.status object.status | |||
| json.created_at format_time(object.created_at) | |||
| json.time_ago time_from_now(object.created_at) | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @organizations.size | |||
| json.organizations @organizations do |org| | |||
| json.partial! "/organizations/organizations/simple", locals: {organization: org} | |||
| end | |||
| @@ -7,4 +7,9 @@ json.project_language_id @project.project_language_id | |||
| json.private !@project.is_public | |||
| json.website @project.website | |||
| json.project_units @project.project_units.pluck(:unit_type) | |||
| json.lesson_url @project.lesson_url | |||
| json.lesson_url @project.lesson_url | |||
| json.permission render_permission(current_user, @project) | |||
| json.is_transfering @project.is_transfering | |||
| json.transfer do | |||
| json.partial! "/users/user_simple", locals: {user: @project&.applied_transfer_project&.owner} | |||
| end | |||
| @@ -1,4 +1,9 @@ | |||
| json.id user.id | |||
| json.name user.real_name | |||
| json.login user.login | |||
| json.image_url url_to_avatar(user) | |||
| if user.present? | |||
| json.id user.id | |||
| json.type user.type | |||
| json.name user.real_name | |||
| json.login user.login | |||
| json.image_url url_to_avatar(user) | |||
| else | |||
| json.nil! | |||
| end | |||
| @@ -0,0 +1,26 @@ | |||
| # project = object.project | |||
| # json.project do | |||
| # json.id project.id | |||
| # json.identifier project.identifier | |||
| # json.name project.name | |||
| # json.description project.description | |||
| # json.is_public project.is_public | |||
| # json.owner do | |||
| # json.partial! "/users/user_simple", locals: {user: project.owner} | |||
| # end | |||
| # end | |||
| # json.user do | |||
| # json.partial! "/users/user_simple", locals: {user: object.user} | |||
| # end | |||
| json.applied do | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: object.applied} | |||
| end | |||
| json.applied_user do | |||
| json.partial! "/users/user_simple", locals: {user: object.applied_user} | |||
| end | |||
| json.applied_type object.applied_type | |||
| json.name object.name | |||
| json.viewed object.viewed | |||
| json.status object.status | |||
| json.created_at format_time(object.created_at) | |||
| json.time_ago time_from_now(object.created_at) | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @applied_messages.total_count | |||
| json.applied_messages @applied_messages do |message| | |||
| json.partial! "/users/applied_messages/detail", locals: {object: message} | |||
| end | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @applied_transfer_projects.total_count | |||
| json.applied_transfer_projects @applied_transfer_projects do |apply| | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: apply} | |||
| end | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -10,6 +10,8 @@ json.user_identity @user.identity | |||
| json.is_watch current_user&.watched?(@user) | |||
| json.watched_count @user.fan_count #粉丝 | |||
| json.watching_count @user.follow_count #关注数 | |||
| json.undo_messages @waiting_applied_messages.size | |||
| json.undo_transfer_projects @common_applied_transfer_projects.size | |||
| json.undo_events @undo_events | |||
| json.user_composes_count @user_composes_count | |||
| json.user_org_count @user_org_count | |||
| @@ -257,6 +257,13 @@ Rails.application.routes.draw do | |||
| end | |||
| scope module: :users do | |||
| resources :applied_messages, only: [:index] | |||
| resources :applied_transfer_projects, only: [:index] do | |||
| member do | |||
| post :accept | |||
| post :refuse | |||
| end | |||
| end | |||
| resources :organizations, only: [:index] | |||
| # resources :projects, only: [:index] | |||
| # resources :subjects, only: [:index] | |||
| @@ -533,6 +540,12 @@ Rails.application.routes.draw do | |||
| scope module: :projects do | |||
| resources :teams, only: [:index, :create, :destroy] | |||
| resources :project_units, only: [:index, :create] | |||
| resources :applied_transfer_projects, only: [:create] do | |||
| collection do | |||
| get :organizations | |||
| post :cancel | |||
| end | |||
| end | |||
| scope do | |||
| get( | |||
| '/blob/*id/diff', | |||
| @@ -0,0 +1,12 @@ | |||
| class CreateAppliedTransferProjects < ActiveRecord::Migration[5.2] | |||
| def change | |||
| create_table :applied_transfer_projects do |t| | |||
| t.references :project | |||
| t.references :owner | |||
| t.references :user | |||
| t.integer :status, default: 0 | |||
| t.timestamps | |||
| end | |||
| end | |||
| end | |||