| @@ -0,0 +1,13 @@ | |||||
| class AppliedProjectsController < ApplicationController | |||||
| before_action :require_login | |||||
| def create | |||||
| @applied_project = Projects::ApplyJoinService.call(current_user, applied_params) | |||||
| rescue Projects::ApplyJoinService::Error => ex | |||||
| render_error(ex.message) | |||||
| end | |||||
| private | |||||
| def applied_params | |||||
| params.require(:applied_project).permit(:code, :role) | |||||
| end | |||||
| end | |||||
| @@ -9,7 +9,7 @@ class Users::AppliedMessagesController < Users::BaseController | |||||
| private | private | ||||
| def check_auth | def check_auth | ||||
| return render_forbidden unless observed_logged_user? | |||||
| return render_forbidden unless current_user.admin? || observed_logged_user? | |||||
| end | end | ||||
| def view_messages | def view_messages | ||||
| @@ -0,0 +1,39 @@ | |||||
| class Users::AppliedProjectsController < Users::BaseController | |||||
| before_action :check_auth | |||||
| before_action :find_applied_project, except: [:index] | |||||
| before_action :find_project, except: [:index] | |||||
| def index | |||||
| @applied_projects = AppliedProject.where(project_id: observed_user.full_admin_projects) | |||||
| @applied_projects = paginate @applied_projects.order("created_at desc") | |||||
| end | |||||
| # 接受申请 | |||||
| def accept | |||||
| @applied_project = Projects::AcceptJoinService.call(current_user, @applied_project) | |||||
| rescue Exception => e | |||||
| uid_logger_error(e.message) | |||||
| tip_exception(e.message) | |||||
| end | |||||
| # 拒绝申请 | |||||
| def refuse | |||||
| @applied_project = Projects::RefuseJoinService.call(current_user, @applied_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? || observed_logged_user? | |||||
| end | |||||
| def find_applied_project | |||||
| @applied_project = AppliedProject.find_by_id params[:id] | |||||
| end | |||||
| def find_project | |||||
| @project = @applied_project.project | |||||
| end | |||||
| end | |||||
| @@ -28,7 +28,7 @@ class Users::AppliedTransferProjectsController < Users::BaseController | |||||
| private | private | ||||
| def check_auth | def check_auth | ||||
| return render_forbidden unless observed_logged_user? | |||||
| return render_forbidden unless current_user.admin? || observed_logged_user? | |||||
| end | end | ||||
| def find_applied_transfer_project | def find_applied_transfer_project | ||||
| @@ -1,5 +1,91 @@ | |||||
| # Projects | # Projects | ||||
| ## 申请加入项目 | |||||
| 申请加入项目 | |||||
| > 示例: | |||||
| ```shell | |||||
| curl -X POST http://localhost:3000/api/applied_projects.json | |||||
| ``` | |||||
| ```javascript | |||||
| await octokit.request('POST /api/appliedr_projects.json') | |||||
| ``` | |||||
| ### HTTP 请求 | |||||
| `POST /api/applied_projects.json` | |||||
| ### 请求参数 | |||||
| 参数 | 必选 | 默认 | 类型 | 字段说明 | |||||
| --------- | ------- | ------- | -------- | ---------- | |||||
| |applied_project.code |是| |string |邀请码 | | |||||
| |applied_project.role |否| |string |项目权限,reporter: 报告者, developer: 开发者,manager:管理员 | | |||||
| > 请求的JSON示例 | |||||
| ```json | |||||
| { | |||||
| "applied_project": { | |||||
| "code": "1una34", | |||||
| "role": "developer" | |||||
| } | |||||
| } | |||||
| ``` | |||||
| ### 返回字段说明 | |||||
| 参数 | 类型 | 字段说明 | |||||
| --------- | ----------- | ----------- | |||||
| |id |int |申请id | | |||||
| |status |string |申请状态,canceled:取消,common:正在申请, accept:已接受,refuse:已拒绝| | |||||
| |time_ago |string |项目申请创建的时间 | | |||||
| |project.id |int |申请项目的id | | |||||
| |project.identifier |string |申请项目的标识 | | |||||
| |project.name |string |申请项目的名称 | | |||||
| |project.description |string |申请项目的描述 | | |||||
| |project.is_public |bool |申请项目是否公开 | | |||||
| |project.owner.id |bool |申请项目拥有者id | | |||||
| |project.owner.type |string |申请项目拥有者类型 | | |||||
| |project.owner.name |string |申请项目拥有者昵称 | | |||||
| |project.owner.login |string |申请项目拥有者标识 | | |||||
| |project.owner.image_url |string |申请项目拥有者头像 | | |||||
| |user.id |int |申请创建者的id | | |||||
| |user.type |string |申请创建者的类型 | | |||||
| |user.name |string |申请创建者的名称 | | |||||
| |user.login |string |申请创建者的标识 | | |||||
| |user.image_url |string |申请创建者头像 | | |||||
| > 返回的JSON示例: | |||||
| ```json | |||||
| { | |||||
| "project": { | |||||
| "id": 74, | |||||
| "identifier": "hehuisssjssjjsjs", | |||||
| "name": "hehuisssjssjjsjs", | |||||
| "description": "wwww", | |||||
| "is_public": false, | |||||
| "owner": { | |||||
| "id": 10, | |||||
| "type": "User", | |||||
| "name": "testforge1", | |||||
| "login": "testforge1", | |||||
| "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png" | |||||
| } | |||||
| }, | |||||
| "user": { | |||||
| "id": 6, | |||||
| "type": "User", | |||||
| "name": "何慧", | |||||
| "login": "yystopf", | |||||
| "image_url": "images/avatars/User/6?t=1622513134" | |||||
| }, | |||||
| "id": 7, | |||||
| "status": "common", | |||||
| "created_at": "2021-06-09 16:41", | |||||
| "time_ago": "1分钟前" | |||||
| } | |||||
| ``` | |||||
| ## 获取项目列表 | ## 获取项目列表 | ||||
| 获取项目列表,也可以更加相关条件过滤搜素 | 获取项目列表,也可以更加相关条件过滤搜素 | ||||
| @@ -1,7 +1,7 @@ | |||||
| <!-- | <!-- | ||||
| * @Date: 2021-03-01 10:35:21 | * @Date: 2021-03-01 10:35:21 | ||||
| * @LastEditors: viletyy | * @LastEditors: viletyy | ||||
| * @LastEditTime: 2021-06-03 10:18:53 | |||||
| * @LastEditTime: 2021-06-09 16:51:18 | |||||
| * @FilePath: /forgeplus/app/docs/slate/source/includes/_users.md | * @FilePath: /forgeplus/app/docs/slate/source/includes/_users.md | ||||
| --> | --> | ||||
| # Users | # Users | ||||
| @@ -1002,11 +1002,11 @@ await octokit.request('GET /api/users/:login/applied_messages.json') | |||||
| |applied.user.name |string |通知主体的迁移创建者的名称 | | |applied.user.name |string |通知主体的迁移创建者的名称 | | ||||
| |applied.user.login |string |通知主体的迁移创建者的标识 | | |applied.user.login |string |通知主体的迁移创建者的标识 | | ||||
| |applied.user.image_url |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_user.id |int |通知发起者的id | | |||||
| |applied_user.type |string |通知发起者的类型 | | |||||
| |applied_user.name |string |通知发起者的名称 | | |||||
| |applied_user.login |string |通知发起者的标识 | | |||||
| |applied_user.image_url |string |通知发起者头像 | | |||||
| |applied_type |string |通知类型 | | |applied_type |string |通知类型 | | ||||
| |name |string | 通知内容 | | |name |string | 通知内容 | | ||||
| |viewed |string|是否已读,waiting:未读,viewed:已读| | |viewed |string|是否已读,waiting:未读,viewed:已读| | ||||
| @@ -1020,6 +1020,48 @@ await octokit.request('GET /api/users/:login/applied_messages.json') | |||||
| { | { | ||||
| "total_count": 5, | "total_count": 5, | ||||
| "applied_messages": [ | "applied_messages": [ | ||||
| { | |||||
| "applied": { | |||||
| "project": { | |||||
| "id": 74, | |||||
| "identifier": "hehuisssjssjjsjs", | |||||
| "name": "hehuisssjssjjsjs", | |||||
| "description": "wwww", | |||||
| "is_public": false, | |||||
| "owner": { | |||||
| "id": 10, | |||||
| "type": "User", | |||||
| "name": "testforge1", | |||||
| "login": "testforge1", | |||||
| "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png" | |||||
| } | |||||
| }, | |||||
| "user": { | |||||
| "id": 6, | |||||
| "type": "User", | |||||
| "name": "何慧", | |||||
| "login": "yystopf", | |||||
| "image_url": "images/avatars/User/6?t=1622513134" | |||||
| }, | |||||
| "id": 6, | |||||
| "status": "accepted", | |||||
| "created_at": "2021-06-09 16:34", | |||||
| "time_ago": "1分钟前" | |||||
| }, | |||||
| "applied_user": { | |||||
| "id": 6, | |||||
| "type": "User", | |||||
| "name": "何慧", | |||||
| "login": "yystopf", | |||||
| "image_url": "images/avatars/User/6?t=1622513134" | |||||
| }, | |||||
| "applied_type": "AppliedProject", | |||||
| "name": "已通过你加入【hehuisssjssjjsjs】仓库的申请。", | |||||
| "viewed": "waiting", | |||||
| "status": "successed", | |||||
| "created_at": "2021-06-09 16:34", | |||||
| "time_ago": "1分钟前" | |||||
| }, | |||||
| { | { | ||||
| "applied": { | "applied": { | ||||
| "project": { | "project": { | ||||
| @@ -1344,4 +1386,240 @@ await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/refus | |||||
| "created_at": "2021-04-25 18:06", | "created_at": "2021-04-25 18:06", | ||||
| "time_ago": "16小时前" | "time_ago": "16小时前" | ||||
| } | } | ||||
| ``` | |||||
| ## 待办事项-项目申请 | |||||
| 待办事项-项目申请 | |||||
| > 示例: | |||||
| ```shell | |||||
| curl -X GET http://localhost:3000/api/users/yystopf/applied_projects.json | |||||
| ``` | |||||
| ```javascript | |||||
| await octokit.request('GET /api/users/:login/applied_projects.json') | |||||
| ``` | |||||
| ### HTTP 请求 | |||||
| `GET /api/users/:login/applied_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 |申请创建者头像 | | |||||
| > 返回的JSON示例: | |||||
| ```json | |||||
| { | |||||
| "total_count": 4, | |||||
| "applied_transfer_projects": [ | |||||
| { | |||||
| "project": { | |||||
| "id": 74, | |||||
| "identifier": "hehuisssjssjjsjs", | |||||
| "name": "hehuisssjssjjsjs", | |||||
| "description": "wwww", | |||||
| "is_public": false, | |||||
| "owner": { | |||||
| "id": 10, | |||||
| "type": "User", | |||||
| "name": "testforge1", | |||||
| "login": "testforge1", | |||||
| "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png" | |||||
| } | |||||
| }, | |||||
| "user": { | |||||
| "id": 6, | |||||
| "type": "User", | |||||
| "name": "何慧", | |||||
| "login": "yystopf", | |||||
| "image_url": "images/avatars/User/6?t=1622513134" | |||||
| }, | |||||
| "id": 7, | |||||
| "status": "common", | |||||
| "created_at": "2021-06-09 16:41", | |||||
| "time_ago": "7分钟前" | |||||
| }, | |||||
| ... | |||||
| ] | |||||
| } | |||||
| ``` | |||||
| ## 用户接受迁移 | |||||
| 用户接受迁移 | |||||
| > 示例: | |||||
| ```shell | |||||
| curl -X POST http://localhost:3000/api/users/yystopf/applied_projects/2/accept.json | |||||
| ``` | |||||
| ```javascript | |||||
| await octokit.request('GET /api/users/:login/applied_projects/:id/accept.json') | |||||
| ``` | |||||
| ### HTTP 请求 | |||||
| `GET /api/users/:login/applied_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 |申请创建者头像 | | |||||
| > 返回的JSON示例: | |||||
| ```json | |||||
| { | |||||
| "project": { | |||||
| "id": 74, | |||||
| "identifier": "hehuisssjssjjsjs", | |||||
| "name": "hehuisssjssjjsjs", | |||||
| "description": "wwww", | |||||
| "is_public": false, | |||||
| "owner": { | |||||
| "id": 10, | |||||
| "type": "User", | |||||
| "name": "testforge1", | |||||
| "login": "testforge1", | |||||
| "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png" | |||||
| } | |||||
| }, | |||||
| "user": { | |||||
| "id": 6, | |||||
| "type": "User", | |||||
| "name": "何慧", | |||||
| "login": "yystopf", | |||||
| "image_url": "images/avatars/User/6?t=1622513134" | |||||
| }, | |||||
| "id": 7, | |||||
| "status": "accept", | |||||
| "created_at": "2021-06-09 16:41", | |||||
| "time_ago": "7分钟前" | |||||
| } | |||||
| ``` | |||||
| ## 用户拒绝迁移 | |||||
| 用户拒绝迁移 | |||||
| > 示例: | |||||
| ```shell | |||||
| curl -X POST http://localhost:3000/api/users/yystopf/applied_projects/2/refuse.json | |||||
| ``` | |||||
| ```javascript | |||||
| await octokit.request('GET /api/users/:login/applied_projects/:id/refuse.json') | |||||
| ``` | |||||
| ### HTTP 请求 | |||||
| `GET /api/users/:login/applied_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 |申请创建者头像 | | |||||
| > 返回的JSON示例: | |||||
| ```json | |||||
| { | |||||
| "project": { | |||||
| "id": 74, | |||||
| "identifier": "hehuisssjssjjsjs", | |||||
| "name": "hehuisssjssjjsjs", | |||||
| "description": "wwww", | |||||
| "is_public": false, | |||||
| "owner": { | |||||
| "id": 10, | |||||
| "type": "User", | |||||
| "name": "testforge1", | |||||
| "login": "testforge1", | |||||
| "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png" | |||||
| } | |||||
| }, | |||||
| "user": { | |||||
| "id": 6, | |||||
| "type": "User", | |||||
| "name": "何慧", | |||||
| "login": "yystopf", | |||||
| "image_url": "images/avatars/User/6?t=1622513134" | |||||
| }, | |||||
| "id": 7, | |||||
| "status": "accept", | |||||
| "created_at": "2021-06-09 16:41", | |||||
| "time_ago": "7分钟前" | |||||
| } | |||||
| ``` | ``` | ||||
| @@ -23,7 +23,7 @@ module Projects | |||||
| ActiveRecord::Base.transaction do | ActiveRecord::Base.transaction do | ||||
| gitea_result = Gitea::Repository::Members::AddService.new(owner, project.identifier, collaborator.login, permission).call | gitea_result = Gitea::Repository::Members::AddService.new(owner, project.identifier, collaborator.login, permission).call | ||||
| if gitea_result.status == 204 | if gitea_result.status == 204 | ||||
| project.add_member!(collaborator.id) | |||||
| project.add_member!(collaborator.id, role_name) | |||||
| end | end | ||||
| fail!(nil) | fail!(nil) | ||||
| end | end | ||||
| @@ -38,5 +38,20 @@ module Projects | |||||
| @error = error | @error = error | ||||
| end | end | ||||
| def role_name | |||||
| case permission | |||||
| when 'read' | |||||
| 'Reporter' | |||||
| when 'write' | |||||
| 'Developer' | |||||
| when 'admin' | |||||
| 'Manager' | |||||
| when 'owner' | |||||
| 'Manager' | |||||
| else | |||||
| 'Reporter' | |||||
| end | |||||
| end | |||||
| end | end | ||||
| end | end | ||||
| @@ -0,0 +1,27 @@ | |||||
| class SendJoinProjectAppliedMessageJob < ApplicationJob | |||||
| queue_as :default | |||||
| def perform(applied_project, applied_user, message_status) | |||||
| project = applied_project.project | |||||
| return unless project.present? | |||||
| return unless applied_user.present? | |||||
| return unless applied_project.user.present? | |||||
| AppliedMessage.find_or_create_by!(user_id: applied_project.user_id, | |||||
| applied: applied_project, | |||||
| status: message_status, | |||||
| name: build_name(project.name, message_status), | |||||
| applied_user_id: applied_user.id, | |||||
| project_id: project.id) | |||||
| end | |||||
| private | |||||
| def build_name(repo_name, message_status, applied_name="") | |||||
| case message_status | |||||
| when 'successed' | |||||
| return "已通过你加入【#{repo_name}】仓库的申请。" | |||||
| when 'failure' | |||||
| return "已拒绝你加入【#{repo_name}】仓库的申请。" | |||||
| end | |||||
| "" | |||||
| end | |||||
| end | |||||
| @@ -7,6 +7,8 @@ | |||||
| # user_id :integer not null | # user_id :integer not null | ||||
| # role :integer default("0") | # role :integer default("0") | ||||
| # status :integer default("0") | # status :integer default("0") | ||||
| # created_at :datetime | |||||
| # updated_at :datetime | |||||
| # | # | ||||
| class AppliedProject < ApplicationRecord | class AppliedProject < ApplicationRecord | ||||
| @@ -16,6 +18,7 @@ class AppliedProject < ApplicationRecord | |||||
| has_many :applied_messages, as: :applied, dependent: :destroy | has_many :applied_messages, as: :applied, dependent: :destroy | ||||
| # has_many :forge_activities, as: :forge_act, dependent: :destroy | # has_many :forge_activities, as: :forge_act, dependent: :destroy | ||||
| enum role: {manager: 3, developer: 4, reporter: 5} | |||||
| enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝 | enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝 | ||||
| end | end | ||||
| @@ -1,41 +1,42 @@ | |||||
| # == Schema Information | |||||
| # | |||||
| # Table name: attachments | |||||
| # | |||||
| # id :integer not null, primary key | |||||
| # container_id :integer | |||||
| # container_type :string(30) | |||||
| # filename :string(255) default(""), not null | |||||
| # disk_filename :string(255) default(""), not null | |||||
| # filesize :integer default("0"), not null | |||||
| # content_type :string(255) default("") | |||||
| # digest :string(60) default(""), not null | |||||
| # downloads :integer default("0"), not null | |||||
| # author_id :integer default("0"), not null | |||||
| # created_on :datetime | |||||
| # description :text(65535) | |||||
| # disk_directory :string(255) | |||||
| # attachtype :integer default("1") | |||||
| # is_public :integer default("1") | |||||
| # copy_from :integer | |||||
| # quotes :integer default("0") | |||||
| # is_publish :integer default("1") | |||||
| # publish_time :datetime | |||||
| # resource_bank_id :integer | |||||
| # unified_setting :boolean default("1") | |||||
| # cloud_url :string(255) default("") | |||||
| # course_second_category_id :integer default("0") | |||||
| # delay_publish :boolean default("0") | |||||
| # | |||||
| # Indexes | |||||
| # | |||||
| # index_attachments_on_author_id (author_id) | |||||
| # index_attachments_on_container_id_and_container_type (container_id,container_type) | |||||
| # index_attachments_on_course_second_category_id (course_second_category_id) | |||||
| # index_attachments_on_created_on (created_on) | |||||
| # index_attachments_on_is_public (is_public) | |||||
| # index_attachments_on_quotes (quotes) | |||||
| # | |||||
| # == Schema Information | |||||
| # | |||||
| # Table name: attachments | |||||
| # | |||||
| # id :integer not null, primary key | |||||
| # container_id :integer | |||||
| # container_type :string(30) | |||||
| # filename :string(255) default(""), not null | |||||
| # disk_filename :string(255) default(""), not null | |||||
| # filesize :integer default("0"), not null | |||||
| # content_type :string(255) default("") | |||||
| # digest :string(60) default(""), not null | |||||
| # downloads :integer default("0"), not null | |||||
| # author_id :integer default("0"), not null | |||||
| # created_on :datetime | |||||
| # description :text(65535) | |||||
| # disk_directory :string(255) | |||||
| # attachtype :integer default("1") | |||||
| # is_public :integer default("1") | |||||
| # copy_from :integer | |||||
| # quotes :integer default("0") | |||||
| # is_publish :integer default("1") | |||||
| # publish_time :datetime | |||||
| # resource_bank_id :integer | |||||
| # unified_setting :boolean default("1") | |||||
| # cloud_url :string(255) default("") | |||||
| # course_second_category_id :integer default("0") | |||||
| # delay_publish :boolean default("0") | |||||
| # | |||||
| # Indexes | |||||
| # | |||||
| # index_attachments_on_author_id (author_id) | |||||
| # index_attachments_on_container_id_and_container_type (container_id,container_type) | |||||
| # index_attachments_on_course_second_category_id (course_second_category_id) | |||||
| # index_attachments_on_created_on (created_on) | |||||
| # index_attachments_on_is_public (is_public) | |||||
| # index_attachments_on_quotes (quotes) | |||||
| # | |||||
| class Attachment < ApplicationRecord | class Attachment < ApplicationRecord | ||||
| include BaseModel | include BaseModel | ||||
| @@ -46,6 +46,10 @@ | |||||
| # is_sync_pwd :boolean default("1") | # is_sync_pwd :boolean default("1") | ||||
| # watchers_count :integer default("0") | # watchers_count :integer default("0") | ||||
| # devops_step :integer default("0") | # devops_step :integer default("0") | ||||
| # sponsor_certification :integer default("0") | |||||
| # sponsor_num :integer default("0") | |||||
| # sponsored_num :integer default("0") | |||||
| # award_time :datetime | |||||
| # | # | ||||
| # Indexes | # Indexes | ||||
| # | # | ||||
| @@ -65,7 +65,7 @@ module ProjectOperable | |||||
| if owner.is_a?(User) | if owner.is_a?(User) | ||||
| managers.exists?(user_id: user.id) | managers.exists?(user_id: user.id) | ||||
| elsif owner.is_a?(Organization) | elsif owner.is_a?(Organization) | ||||
| managers.exists?(user_id: user.id) || owner.is_admin?(user.id) | |||||
| managers.exists?(user_id: user.id) || owner.is_only_admin?(user.id) | |||||
| else | else | ||||
| false | false | ||||
| end | end | ||||
| @@ -76,7 +76,7 @@ module ProjectOperable | |||||
| if owner.is_a?(User) | if owner.is_a?(User) | ||||
| developers.exists?(user_id: user.id) | developers.exists?(user_id: user.id) | ||||
| elsif owner.is_a?(Organization) | elsif owner.is_a?(Organization) | ||||
| developers.exists?(user_id: user.id) || owner.is_write?(user.id) | |||||
| developers.exists?(user_id: user.id) || owner.is_only_write?(user.id) | |||||
| else | else | ||||
| false | false | ||||
| end | end | ||||
| @@ -6,7 +6,7 @@ | |||||
| # tracker_id :integer not null | # tracker_id :integer not null | ||||
| # project_id :integer not null | # project_id :integer not null | ||||
| # subject :string(255) default(""), not null | # subject :string(255) default(""), not null | ||||
| # description :text(65535) | |||||
| # description :text(4294967295) | |||||
| # due_date :date | # due_date :date | ||||
| # category_id :integer | # category_id :integer | ||||
| # status_id :integer not null | # status_id :integer not null | ||||
| @@ -14,7 +14,6 @@ | |||||
| # priority_id :integer not null | # priority_id :integer not null | ||||
| # fixed_version_id :integer | # fixed_version_id :integer | ||||
| # author_id :integer not null | # author_id :integer not null | ||||
| # lock_version :integer default("0"), not null | |||||
| # created_on :datetime | # created_on :datetime | ||||
| # updated_on :datetime | # updated_on :datetime | ||||
| # start_date :date | # start_date :date | ||||
| @@ -28,7 +27,7 @@ | |||||
| # closed_on :datetime | # closed_on :datetime | ||||
| # project_issues_index :integer | # project_issues_index :integer | ||||
| # issue_type :string(255) | # issue_type :string(255) | ||||
| # token :string(255) | |||||
| # token :integer default("0") | |||||
| # issue_tags_value :string(255) | # issue_tags_value :string(255) | ||||
| # is_lock :boolean default("0") | # is_lock :boolean default("0") | ||||
| # issue_classify :string(255) | # issue_classify :string(255) | ||||
| @@ -7,6 +7,7 @@ | |||||
| # content :text(65535) | # content :text(65535) | ||||
| # created_at :datetime not null | # created_at :datetime not null | ||||
| # updated_at :datetime not null | # updated_at :datetime not null | ||||
| # is_secret :boolean default("0") | |||||
| # | # | ||||
| class License < ApplicationRecord | class License < ApplicationRecord | ||||
| @@ -11,6 +11,7 @@ | |||||
| # course_group_id :integer default("0") | # course_group_id :integer default("0") | ||||
| # is_collect :integer default("1") | # is_collect :integer default("1") | ||||
| # graduation_group_id :integer default("0") | # graduation_group_id :integer default("0") | ||||
| # is_apply_signature :boolean default("0") | |||||
| # | # | ||||
| # Indexes | # Indexes | ||||
| # | # | ||||
| @@ -46,6 +46,10 @@ | |||||
| # is_sync_pwd :boolean default("1") | # is_sync_pwd :boolean default("1") | ||||
| # watchers_count :integer default("0") | # watchers_count :integer default("0") | ||||
| # devops_step :integer default("0") | # devops_step :integer default("0") | ||||
| # sponsor_certification :integer default("0") | |||||
| # sponsor_num :integer default("0") | |||||
| # sponsored_num :integer default("0") | |||||
| # award_time :datetime | |||||
| # | # | ||||
| # Indexes | # Indexes | ||||
| # | # | ||||
| @@ -106,6 +110,14 @@ class Organization < Owner | |||||
| team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).present? | team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).present? | ||||
| end | end | ||||
| def is_only_admin?(user_id) | |||||
| team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(admin)}).present? | |||||
| end | |||||
| def is_only_write?(user_id) | |||||
| team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(write)}).present? | |||||
| end | |||||
| def is_only_read?(user_id) | def is_only_read?(user_id) | ||||
| team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read)}).present? | team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read)}).present? | ||||
| end | end | ||||
| @@ -1,19 +1,20 @@ | |||||
| # == Schema Information | |||||
| # | |||||
| # Table name: praise_treads | |||||
| # | |||||
| # id :integer not null, primary key | |||||
| # user_id :integer not null | |||||
| # praise_tread_object_id :integer | |||||
| # praise_tread_object_type :string(255) | |||||
| # praise_or_tread :integer default("1") | |||||
| # created_at :datetime not null | |||||
| # updated_at :datetime not null | |||||
| # | |||||
| # Indexes | |||||
| # | |||||
| # praise_tread (praise_tread_object_id,praise_tread_object_type) | |||||
| # | |||||
| # == Schema Information | |||||
| # | |||||
| # Table name: praise_treads | |||||
| # | |||||
| # id :integer not null, primary key | |||||
| # user_id :integer not null | |||||
| # praise_tread_object_id :integer | |||||
| # praise_tread_object_type :string(255) | |||||
| # praise_or_tread :integer default("1") | |||||
| # created_at :datetime not null | |||||
| # updated_at :datetime not null | |||||
| # | |||||
| # Indexes | |||||
| # | |||||
| # praise_tread (praise_tread_object_id,praise_tread_object_type) | |||||
| # | |||||
| class PraiseTread < ApplicationRecord | class PraiseTread < ApplicationRecord | ||||
| belongs_to :user | belongs_to :user | ||||
| @@ -4,7 +4,7 @@ | |||||
| # | # | ||||
| # id :integer not null, primary key | # id :integer not null, primary key | ||||
| # name :string(255) default(""), not null | # name :string(255) default(""), not null | ||||
| # description :text(65535) | |||||
| # description :text(4294967295) | |||||
| # homepage :string(255) default("") | # homepage :string(255) default("") | ||||
| # is_public :boolean default("1"), not null | # is_public :boolean default("1"), not null | ||||
| # parent_id :integer | # parent_id :integer | ||||
| @@ -43,6 +43,19 @@ | |||||
| # watchers_count :integer default("0") | # watchers_count :integer default("0") | ||||
| # issues_count :integer default("0") | # issues_count :integer default("0") | ||||
| # pull_requests_count :integer default("0") | # pull_requests_count :integer default("0") | ||||
| # language :string(255) | |||||
| # versions_count :integer default("0") | |||||
| # issue_tags_count :integer default("0") | |||||
| # closed_issues_count :integer default("0") | |||||
| # open_devops :boolean default("0") | |||||
| # gitea_webhook_id :integer | |||||
| # open_devops_count :integer default("0") | |||||
| # recommend :boolean default("0") | |||||
| # platform :integer default("0") | |||||
| # default_branch :string(255) default("master") | |||||
| # website :string(255) | |||||
| # order_index :integer default("0") | |||||
| # lesson_url :string(255) | |||||
| # | # | ||||
| # Indexes | # Indexes | ||||
| # | # | ||||
| @@ -16,6 +16,11 @@ | |||||
| # head :string(255) | # head :string(255) | ||||
| # base :string(255) | # base :string(255) | ||||
| # issue_id :integer | # issue_id :integer | ||||
| # fork_project_id :integer | |||||
| # is_original :boolean default("0") | |||||
| # comments_count :integer default("0") | |||||
| # commits_count :integer default("0") | |||||
| # files_count :integer default("0") | |||||
| # | # | ||||
| class PullRequest < ApplicationRecord | class PullRequest < ApplicationRecord | ||||
| @@ -46,6 +46,10 @@ | |||||
| # is_sync_pwd :boolean default("1") | # is_sync_pwd :boolean default("1") | ||||
| # watchers_count :integer default("0") | # watchers_count :integer default("0") | ||||
| # devops_step :integer default("0") | # devops_step :integer default("0") | ||||
| # sponsor_certification :integer default("0") | |||||
| # sponsor_num :integer default("0") | |||||
| # sponsored_num :integer default("0") | |||||
| # award_time :datetime | |||||
| # | # | ||||
| # Indexes | # Indexes | ||||
| # | # | ||||
| @@ -206,6 +210,13 @@ class User < Owner | |||||
| return Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct | return Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct | ||||
| end | end | ||||
| # 用户管理的所有项目 | |||||
| def full_admin_projects | |||||
| normal_projects = Project.joins(members: :roles).where(roles: {name: 'Manager'}, members: {user_id: self.id}).to_sql | |||||
| org_projects = Project.joins(teams: :team_users).where(teams: {authorize: %w(admin owner)}, team_users: {user_id: self.id}).to_sql | |||||
| return Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct | |||||
| end | |||||
| def name | def name | ||||
| login | login | ||||
| end | end | ||||
| @@ -0,0 +1,61 @@ | |||||
| class Projects::AcceptJoinService < ApplicationService | |||||
| attr_accessor :applied_project, :owner | |||||
| attr_reader :user, :project | |||||
| def initialize(user, applied_project) | |||||
| @user = user | |||||
| @project = applied_project.project | |||||
| @applied_project = applied_project | |||||
| end | |||||
| def call | |||||
| Rails.logger.info("###### Project accept_join_service begin ######") | |||||
| ActiveRecord::Base.transaction do | |||||
| validate! | |||||
| update_apply | |||||
| operate_project_member | |||||
| send_apply_message | |||||
| end | |||||
| Rails.logger.info("##### Project accept_join_service end ######") | |||||
| return @applied_project | |||||
| end | |||||
| private | |||||
| def permission | |||||
| case @applied_project.role | |||||
| when 'manager' | |||||
| 'admin' | |||||
| when 'developer' | |||||
| 'write' | |||||
| when 'reporter' | |||||
| 'read' | |||||
| else | |||||
| 'read' | |||||
| end | |||||
| end | |||||
| def validate! | |||||
| raise Error, '该申请已经被接受' if @applied_project.accepted? | |||||
| raise Error, '该申请不存在' unless @applied_project.present? | |||||
| raise Error, '未拥有接受申请权限' unless is_permit_operator | |||||
| end | |||||
| def is_permit_operator | |||||
| return @user.admin? || @project.manager?(@user) | |||||
| end | |||||
| def update_apply | |||||
| @applied_project.update!(status: 'accepted') | |||||
| end | |||||
| def operate_project_member | |||||
| Projects::AddMemberInteractor.call(@project.owner, @project, @user, permission) | |||||
| end | |||||
| def send_apply_message | |||||
| SendJoinProjectAppliedMessageJob.perform_now(@applied_project, @user, 'successed') | |||||
| end | |||||
| end | |||||
| @@ -9,26 +9,25 @@ class Projects::ApplyJoinService < ApplicationService | |||||
| end | end | ||||
| def call | def call | ||||
| validate! | |||||
| # 项目报告人员直接加入项目 | # 项目报告人员直接加入项目 | ||||
| if params[:role] == 'reporter' | |||||
| # Projects::JoinService.call(project, user, role: 'reporter') | |||||
| return project | |||||
| end | |||||
| # if params[:role] == 'reporter' | |||||
| # # Projects::JoinService.call(project, user, role: 'reporter') | |||||
| # return project | |||||
| # end | |||||
| ActiveRecord::Base.transaction do | ActiveRecord::Base.transaction do | ||||
| validate! | |||||
| apply = user.applied_projects.create!(project: project, role: role_value) | apply = user.applied_projects.create!(project: project, role: role_value) | ||||
| apply | |||||
| # apply.forge_activities.find_or_create_by!(user: user, project: project) | |||||
| apply.forge_activities.find_or_create_by!(user: user, project: project) | |||||
| notify_project_manager!(apply) | |||||
| # notify_project_manager!(apply) | |||||
| end | end | ||||
| # notify_project_owner | # notify_project_owner | ||||
| ApplyJoinProjectNotifyJob.perform_later(user.id, project.id, role_value) | |||||
| # ApplyJoinProjectNotifyJob.perform_later(user.id, project.id, role_value) | |||||
| project | |||||
| end | end | ||||
| private | private | ||||
| @@ -43,7 +42,8 @@ class Projects::ApplyJoinService < ApplicationService | |||||
| when 'manager' then 3 | when 'manager' then 3 | ||||
| when 'developer' then 4 | when 'developer' then 4 | ||||
| when 'reporter' then 5 | when 'reporter' then 5 | ||||
| else raise Error, '角色无效' | |||||
| else | |||||
| 5 | |||||
| end | end | ||||
| end | end | ||||
| @@ -74,12 +74,18 @@ class Projects::ApplyJoinService < ApplicationService | |||||
| def validate! | def validate! | ||||
| # params check | # params check | ||||
| raise Error, '邀请码不能为空' if params[:code].blank? | raise Error, '邀请码不能为空' if params[:code].blank? | ||||
| raise Error, '角色不能为空' if params[:role].blank? | |||||
| raise Error, '角色无效' unless %w(manager developer reporter).include?(params[:role]) | |||||
| raise Error, '请输入6位项目邀请码' unless valid_invite_code( params[:code]) | |||||
| # logical check | # logical check | ||||
| raise Error, '邀请码无效' if project.blank? | raise Error, '邀请码无效' if project.blank? | ||||
| raise Error, '您已在该项目中' if project.member?(user) | |||||
| raise Error, '您已经提交过申请' if user.applied_projects.pending.exists?(project: project) | |||||
| raise Error, '您已是项目成员' if project.member?(user) | |||||
| raise Error, '您已经提交过申请' if user.applied_projects.common.exists?(project: project) | |||||
| end | |||||
| def valid_invite_code(str) | |||||
| if (str =~ /^[A-Za-z0-9]{6}+$/) | |||||
| return true | |||||
| end | |||||
| return false | |||||
| end | end | ||||
| end | end | ||||
| @@ -0,0 +1,39 @@ | |||||
| class Projects::RefuseJoinService < ApplicationService | |||||
| attr_accessor :applied_project, :owner | |||||
| attr_reader :user, :project | |||||
| def initialize(user, applied_project) | |||||
| @user = user | |||||
| @project = applied_project.project | |||||
| @applied_project = applied_project | |||||
| end | |||||
| def call | |||||
| Rails.logger.info("###### Project refuse_join_service begin ######") | |||||
| validate! | |||||
| update_apply | |||||
| send_apply_message | |||||
| Rails.logger.info("###### Project refuse_join_service end ######") | |||||
| return @applied_project | |||||
| end | |||||
| private | |||||
| def validate! | |||||
| raise Error, '该申请已被拒绝' if @applied_project.refused? | |||||
| raise Error, '该申请不存在' unless @applied_project.present? | |||||
| raise Error, '未拥有接受申请权限' unless is_permit_operator | |||||
| end | |||||
| def is_permit_operator | |||||
| return @user.admin? || @project.manager?(@user) | |||||
| end | |||||
| def update_apply | |||||
| @applied_project.update!(status: 'refused') | |||||
| end | |||||
| def send_apply_message | |||||
| SendJoinProjectAppliedMessageJob.perform_now(@applied_project, @user, 'failure') | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,18 @@ | |||||
| 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.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! "detail", locals: {object: @applied_project} | |||||
| @@ -13,7 +13,12 @@ | |||||
| # json.partial! "/users/user_simple", locals: {user: object.user} | # json.partial! "/users/user_simple", locals: {user: object.user} | ||||
| # end | # end | ||||
| json.applied do | json.applied do | ||||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: object.applied} | |||||
| case object.applied_type | |||||
| when 'AppliedTransferProject' | |||||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: object.applied} | |||||
| when 'AppliedProject' | |||||
| json.partial! "/applied_projects/detail", locals: {object: object.applied} | |||||
| end | |||||
| end | end | ||||
| json.applied_user do | json.applied_user do | ||||
| json.partial! "/users/user_simple", locals: {user: object.applied_user} | json.partial! "/users/user_simple", locals: {user: object.applied_user} | ||||
| @@ -0,0 +1 @@ | |||||
| json.partial! "/applied_projects/detail", locals: {object: @applied_project} | |||||
| @@ -0,0 +1,4 @@ | |||||
| json.total_count @applied_projects.total_count | |||||
| json.applied_projects @applied_projects do |apply| | |||||
| json.partial! "/applied_projects/detail", locals: {object: apply} | |||||
| end | |||||
| @@ -0,0 +1 @@ | |||||
| json.partial! "/applied_projects/detail", locals: {object: @applied_project} | |||||
| @@ -148,6 +148,8 @@ Rails.application.routes.draw do | |||||
| resources :issue_depends, only: [:create, :destroy] | resources :issue_depends, only: [:create, :destroy] | ||||
| end | end | ||||
| resources :applied_projects, only: [:create] | |||||
| resources :project_categories, only: [:index, :show] do | resources :project_categories, only: [:index, :show] do | ||||
| get :group_list, on: :collection | get :group_list, on: :collection | ||||
| end | end | ||||
| @@ -266,6 +268,12 @@ Rails.application.routes.draw do | |||||
| post :refuse | post :refuse | ||||
| end | end | ||||
| end | end | ||||
| resources :applied_projects, only: [:index] do | |||||
| member do | |||||
| post :accept | |||||
| post :refuse | |||||
| end | |||||
| end | |||||
| resources :headmaps, only: [:index] | resources :headmaps, only: [:index] | ||||
| resources :is_pinned_projects, only: [:index, :update] do | resources :is_pinned_projects, only: [:index, :update] do | ||||
| collection do | collection do | ||||
| @@ -0,0 +1,5 @@ | |||||
| class AddTimestampToAppliedProjects < ActiveRecord::Migration[5.2] | |||||
| def change | |||||
| add_timestamps(:applied_projects, null: true) | |||||
| end | |||||
| end | |||||