merge conflict

This commit is contained in:
nigel007 2021-05-11 21:38:22 +08:00
commit c069bb0544
83 changed files with 3049 additions and 209 deletions

View File

@ -1,5 +1,22 @@
# Changelog
## [v3.0.3](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-08
* BUGFIXES
* Fix 解决易修标题过长导致的排版问题(45469)
* Fix 解决合并请求详情页面排版错误的问题(45457)
* FIX 解决转移仓库界面专有名词描述错误的问题(45455)
* Fix 解决markdown格式文件自动生成数字排序的问题(45454)
* Fix 解决镜像项目源地址不显示的问题(45403)
* Fix 解决镜像项目导航显示错误问题(45398)
* Fix 解决其他相关bug
* ENHANCEMENTS
* UPDATE 用户注册时,账号和密码正则匹配调整(45336) (45318) (45290)
* ADD 创建组织各属性添加规则匹配功能(45313) (45289)
* ADD 创建团建各属性添加规则匹配功能(45334) (45325) (45287)
* ADD 仓库转移功能(45017) (45015)
## [v3.0.2](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-04-23
* BUGFIXES

View File

@ -169,6 +169,7 @@ class AccountsController < ApplicationController
# 用户登录
def login
Users::LoginForm.new(account_params).validate!
@user = User.try_to_login(params[:login], params[:password])
return normal_status(-2, "错误的账号或密码") if @user.blank?
@ -345,4 +346,7 @@ class AccountsController < ApplicationController
params.require(:user).permit(:login, :email, :phone)
end
def account_params
params.require(:account).permit(:login, :password)
end
end

View File

@ -0,0 +1,28 @@
module Acceleratorable
extend ActiveSupport::Concern
def enable_accelerator?(clone_addr)
clone_addr.include?(github_domain) || clone_addr.include?(gitlab_domain)
end
def accelerator_url(repo_name)
[accelerator_domain, accelerator_username, "#{repo_name}.git"].join('/')
end
def github_domain
'github.com'
end
def gitlab_domain
'gitlab.com'
end
def accelerator_domain
Gitea.gitea_config[:accelerator]["domain"]
end
def accelerator_username
Gitea.gitea_config[:accelerator]["access_key_id"]
end
end

View File

@ -101,14 +101,8 @@ class IssuesController < ApplicationController
end
def create
if params[:subject].blank?
normal_status(-1, "标题不能为空")
elsif params[:subject].to_s.size > 255
normal_status(-1, "标题不能超过255个字符")
else
ActiveRecord::Base.transaction do
issue_params = issue_send_params(params)
Issues::CreateForm.new({subject:issue_params[:subject]}).validate!
@issue = Issue.new(issue_params)
if @issue.save!
if params[:attachment_ids].present?
@ -158,13 +152,14 @@ class IssuesController < ApplicationController
#else
# render json: {status: 0, message: "创建成功", id: @issue.id}
#end
render json: {status: 0, message: "创建成", id: @issue.id}
else
normal_status(-1, "创建失败")
raise ActiveRecord::Rollback
end
end
end
rescue Exception => exception
puts exception.message
normal_status(-1, exception.message)
end
def edit
@ -218,7 +213,7 @@ class IssuesController < ApplicationController
normal_status(-1, "不允许修改为关闭状态")
else
issue_params = issue_send_params(params).except(:issue_classify, :author_id, :project_id)
Issues::UpdateForm.new({subject:issue_params[:subject]}).validate!
if @issue.update_attributes(issue_params)
if params[:status_id].to_i == 5 #任务由非关闭状态到关闭状态时
@issue.issue_times.update_all(end_time: Time.now)
@ -244,6 +239,9 @@ class IssuesController < ApplicationController
normal_status(-1, "更新失败")
end
end
rescue Exception => exception
puts exception.message
normal_status(-1, exception.message)
end
def show

View File

@ -17,6 +17,7 @@ class Organizations::BaseController < ApplicationController
end
def org_privacy_condition
return false if current_user.admin?
@organization.organization_extension.privacy? && @organization.organization_users.where(user_id: current_user.id).blank?
end

View File

@ -36,6 +36,7 @@ class Organizations::OrganizationsController < Organizations::BaseController
def update
ActiveRecord::Base.transaction do
Organizations::CreateForm.new(organization_params).validate!
login = @organization.login
@organization.login = organization_params[:name] if organization_params[:name].present?
@organization.nickname = organization_params[:nickname] if organization_params[:nickname].present?

View File

@ -43,6 +43,7 @@ class Organizations::TeamsController < Organizations::BaseController
end
def update
Organizations::CreateTeamForm.new(team_params).validate!
@team = Organizations::Teams::UpdateService.call(current_user, @team, team_params)
rescue Exception => e
uid_logger_error(e.message)

View File

@ -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

View File

@ -2,6 +2,8 @@ class ProjectsController < ApplicationController
include ApplicationHelper
include OperateProjectAbilityAble
include ProjectsHelper
include Acceleratorable
before_action :require_login, except: %i[index branches group_type_list simple show fork_users praise_users watch_users recommend about menu_list]
before_action :load_project, except: %i[index group_type_list migrate create recommend]
before_action :authorizate_user_can_edit_project!, only: %i[update]
@ -53,7 +55,23 @@ class ProjectsController < ApplicationController
def migrate
Projects::MigrateForm.new(mirror_params).validate!
@project = Projects::MigrateService.new(current_user, mirror_params).call
@project =
if enable_accelerator?(mirror_params[:clone_addr])
source_clone_url = mirror_params[:clone_addr]
uid_logger("########## 已动加速器 ##########")
result = Gitea::Accelerator::MigrateService.call(mirror_params)
if result[:status] == :success
Rails.logger.info "########## 加速镜像成功 ########## "
Projects::MigrateService.call(current_user,
mirror_params.merge(source_clone_url: source_clone_url,
clone_addr: accelerator_url(mirror_params[:repository_name])))
else
Projects::MigrateService.call(current_user, mirror_params)
end
else
Projects::MigrateService.call(current_user, mirror_params)
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
@ -88,7 +106,7 @@ class ProjectsController < ApplicationController
def update
ActiveRecord::Base.transaction do
# Projects::CreateForm.new(project_params).validate!
Projects::UpdateForm.new(project_params).validate!
private = params[:private] || false
new_project_params = project_params.except(:private).merge(is_public: !private)
@ -149,7 +167,7 @@ class ProjectsController < ApplicationController
end
def recommend
@projects = Project.recommend.includes(:repository, :project_category, :owner).limit(5)
@projects = Project.recommend.includes(:repository, :project_category, :owner).order(id: :desc).limit(5)
end
def about

View File

@ -160,7 +160,7 @@ class PullRequestsController < ApplicationController
end
def pr_merge
return render_forbidden("你没有权限操作.") unless current_user.project_manager?(@project)
return render_forbidden("你没有权限操作.") unless @project.operator?(current_user)
if params[:do].blank?
normal_status(-1, "请选择合并方式")
@ -169,7 +169,7 @@ class PullRequestsController < ApplicationController
begin
result = PullRequests::MergeService.call(@owner, @repository, @pull_request, current_user, params)
if result && @pull_request.merge!
if result.status == 200 && @pull_request.merge!
@pull_request.project_trend_status!
@issue&.custom_journal_detail("merge", "", "该合并请求已被合并", current_user&.id)
@ -216,7 +216,7 @@ class PullRequestsController < ApplicationController
end
end
else
normal_status(-1, "合并失败")
normal_status(-1, result.message)
end
rescue => e
normal_status(-1, e.message)
@ -277,7 +277,7 @@ class PullRequestsController < ApplicationController
def get_relatived
@project_tags = @project.issue_tags&.select(:id,:name, :color).as_json
@project_versions = @project.versions&.select(:id,:name, :status).as_json
@project_members = @project.members_user_infos
@project_members = @project.all_developers
@project_priories = IssuePriority&.select(:id,:name, :position).as_json
end

View File

@ -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

View File

@ -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

View File

@ -27,12 +27,24 @@ class UsersController < ApplicationController
def show
#待办事项,现在未做
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_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? @user.projects : @user.projects.visible
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
normal_projects = Project.members_projects(@user.id).to_sql
org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql
projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible
@projects_common_count = user_projects.common.size
@projects_mirrior_count = user_projects.mirror.size
@projects_sync_mirrior_count = user_projects.sync_mirror.size

View File

@ -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分钟前"
}
```

View File

@ -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小时前"
}
```

View File

@ -0,0 +1,11 @@
class Issues::CreateForm
include ActiveModel::Model
attr_accessor :subject
validates :subject, presence: { message: "不能为空" }
validates :subject, length: { maximum: 80, too_long: "不能超过80个字符" }
end

View File

@ -0,0 +1,10 @@
class Issues::UpdateForm
include ActiveModel::Model
attr_accessor :subject
validates :subject, presence: { message: "不能为空" }
validates :subject, length: { maximum: 80, too_long: "不能超过80个字符" }
end

View File

@ -3,6 +3,9 @@ class Organizations::CreateForm < BaseForm
attr_accessor :name, :description, :website, :location, :repo_admin_change_team_access, :visibility, :max_repo_creation, :nickname
validates :name, :nickname, :visibility, presence: true
validates :name, :nickname, length: { maximum: 100 }
validates :location, length: { maximum: 50 }
validates :description, length: { maximum: 200 }
validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
end

View File

@ -2,7 +2,9 @@ class Organizations::CreateTeamForm < BaseForm
NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
attr_accessor :name, :nickname, :description, :authorize, :includes_all_project, :can_create_org_project, :unit_types
validates :name, :nickname, :authorize, presence: true
validates :name, :nickname, presence: true
validates :name, :nickname, length: { maximum: 100 }
validates :description, length: { maximum: 200 }
validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
end

View File

@ -3,11 +3,14 @@ class Projects::CreateForm < BaseForm
attr_accessor :user_id, :name, :description, :repository_name, :project_category_id,
:project_language_id, :ignore_id, :license_id, :private, :owner,
:blockchain, :blockchain_token_all, :blockchain_init_token
validates :user_id, :name, :description,:repository_name,
:project_category_id, :project_language_id, presence: true
validates :repository_name, format: { with: REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
validates :name, length: { maximum: 50 }
validates :repository_name, length: { maximum: 100 }
validates :description, length: { maximum: 200 }
validate :check_ignore, :check_license, :check_owner, :check_max_repo_creation
validate do
check_project_category(project_category_id)

View File

@ -1,4 +1,11 @@
class Projects::UpdateForm < BaseForm
attr_reader :name, :description, :repository_name, :project_category_id
attr_accessor :name, :description, :project_category_id, :project_language_id, :private
validates :name, :description, :project_category_id, :project_language_id, presence: true
validates :name, length: { maximum: 50 }
validates :description, length: { maximum: 200 }
validate do
check_project_category(project_category_id)
check_project_language(project_language_id)
end
end

View File

@ -0,0 +1,8 @@
class Users::LoginForm
include ActiveModel::Model
attr_accessor :password, :login
validates :login, presence: true
validates :password, presence: true, length: { minimum: 8, maximum: 16 }, format: { with: CustomRegexp::PASSWORD, message: "8~16位支持字母数字和符号" }
end

View File

@ -0,0 +1,14 @@
module Admins::ProjectsHelper
def link_to_project(project)
owner = project.owner
if owner.is_a?(User)
link_to(project.owner&.real_name, "/users/#{project&.owner&.login}", target: '_blank')
elsif owner.is_a?(Organization)
link_to(project.owner&.real_name, "/organize/#{project&.owner&.login}", target: '_blank')
else
""
end
end
end

View File

@ -34,15 +34,14 @@ module ProjectsHelper
end
def json_response(project, user)
# repo = project.repository
repo = Repository.includes(:mirror).select(:id, :mirror_url).find_by(project: project)
repo = Repository.includes(:mirror).select(:id, :mirror_url, :source_clone_url).find_by(project: project)
tmp_json = {}
unless project.common?
tmp_json = tmp_json.merge({
mirror_status: repo.mirror_status,
mirror_num: repo.mirror_num,
mirror_url: repo.mirror_url,
mirror_url: repo.remote_mirror_url,
first_sync: repo.first_sync?
})
end

View File

@ -124,14 +124,13 @@ module TagChosenHelper
end
def render_cache_collaborators(project)
cache_key = "all_collaborators/#{project.members.maximum('created_on')}"
cache_key = "all_collaborators/#{project.all_collaborators.maximum('created_on')}"
Rails.cache.fetch(cache_key) do
project.members.includes(:user).collect do |event|
project.all_collaborators.order(created_on: :desc).collect do |user|
{
id: event.user&.id,
name: event.user&.show_real_name,
avatar_url: url_to_avatar(event.user),
id: user&.id,
name: user&.show_real_name,
avatar_url: url_to_avatar(user),
is_chosen: '0'
}
end
@ -171,10 +170,8 @@ module TagChosenHelper
# depended_issues_id = @depended_issues_id
end
project_members = project.members_user_infos
project_members_info = [] #指派给
project_members.includes(user: :user_extension).each do |member|
user = member&.user
project.all_collaborators.includes(:user_extension).each do |user|
if user
real_name = user.try(:show_real_name)
user_id = user.id

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -11,6 +11,14 @@ module ProjectOperable
has_many :team_projects, dependent: :destroy
end
def set_owner_permission(creator)
return unless owner.is_a?(Organization)
owner.build_permit_team_projects!(id)
# 避免自己创建的项目,却无法拥有访问权,因为该用户所在团队暂未获得项目访问权
return if creator.nil? || owner.is_owner?(creator.id)
add_member!(creator.id, "Manager")
end
def add_member!(user_id, role_name='Developer')
member = members.create!(user_id: user_id)
set_developer_role(member, role_name)
@ -78,12 +86,16 @@ module ProjectOperable
if owner.is_a?(User)
reporters.exists?(user_id: user.id)
elsif owner.is_a?(Organization)
reporters.exists?(user_id: user.id) || owner.is_read?(user.id)
reporters.exists?(user_id: user.id) || owner.is_only_read?(user.id)
else
false
end
end
def operator?(user)
user.admin? || !reporter?(user)
end
def set_developer_role(member, role_name)
role = Role.find_by(name: role_name)
member.member_roles.create!(role: role)
@ -92,4 +104,22 @@ module ProjectOperable
def has_menu_permission(unit_type)
self.project_units.where(unit_type: unit_type).exists?
end
def all_collaborators
member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager Developer Reporter)}).to_sql
team_user_sql = User.joins(teams: :team_projects).where(team_projects: {project_id: self.id}).to_sql
return User.from("( #{ member_sql } UNION #{ team_user_sql } ) AS users").distinct
end
def all_developers
member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager Developer)}).to_sql
team_user_sql = User.joins(teams: :team_projects).where(teams: {authorize: %w(owner admin write)}, team_projects: {project_id: self.id}).to_sql
return User.from("( #{ member_sql } UNION #{ team_user_sql } ) AS users").distinct
end
def all_managers
member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager)}).to_sql
team_user_sql = User.joins(teams: :team_projects).where(teams: {authorize: %w(owner admin)},team_projects: {project_id: self.id}).to_sql
return User.from("( #{ member_sql} UNION #{ team_user_sql } ) AS users").distinct
end
end

View File

@ -106,12 +106,23 @@ class Organization < Owner
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).present?
end
def is_only_read?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read)}).present?
end
# 是不是所有者团队的最后一个成员
def is_owner_team_last_one?(user_id)
owner_team_users = team_users.joins(:team).where(teams: {authorize: %w(owner)})
owner_team_users.pluck(:user_id).include?(user_id) && owner_team_users.size == 1
end
# 为包含组织所有项目的团队创建项目访问权限
def build_permit_team_projects!(project_id)
teams.where(includes_all_project: true).each do |team|
TeamProject.build(id, team.id, project_id)
end
end
def real_name
name = lastname + firstname
name = name.blank? ? (nickname.blank? ? login : nickname) : name

View File

@ -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

View File

@ -37,8 +37,6 @@
# rep_identifier :string(255)
# project_category_id :integer
# project_language_id :integer
# license_id :integer
# ignore_id :integer
# praises_count :integer default("0")
# watchers_count :integer default("0")
# issues_count :integer default("0")
@ -53,6 +51,8 @@
# recommend :boolean default("0")
# platform :integer default("0")
# use_blockchain :boolean default("0")
# license_id :integer
# ignore_id :integer
# default_branch :string(255) default("master")
# website :string(255)
# lesson_url :string(255)
@ -114,6 +114,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, :use_blockchain)}
@ -297,4 +298,7 @@ class Project < ApplicationRecord
update_column(:updated_on, time)
end
def is_transfering
applied_transfer_project&.common? ? true : false
end
end

View File

@ -20,13 +20,18 @@ class ProjectUnit < ApplicationRecord
validates :unit_type, uniqueness: { scope: :project_id}
def self.init_types(project_id)
ProjectUnit::unit_types.each do |_, v|
def self.init_types(project_id, project_type='common')
unit_types = project_type == 'sync_mirror' ? ProjectUnit::unit_types.except("pulls") : ProjectUnit::unit_types
unit_types.each do |_, v|
self.create!(project_id: project_id, unit_type: v)
end
end
def self.update_by_unit_types!(project, types)
# 同步镜像项目不能有合并请求模块
types.delete("pulls") if project.sync_mirror?
# 默认code类型自动创建
types << "code"
project.project_units.where.not(unit_type: types).each(&:destroy!)
types.each do |type|
project.project_units.find_or_create_by!(unit_type: type)

View File

@ -22,6 +22,8 @@
# version_releases_count :integer default("0")
# fork_url :string(255)
# is_mirror :boolean default("0")
# accelerator_url :string(255) default("")
# source_clone_url :string(255) default("")
#
# Indexes
#
@ -77,4 +79,9 @@ class Repository < ApplicationRecord
end
end
def remote_mirror_url
source_clone_url.blank? ? mirror_url : source_clone_url
end
end

View File

@ -33,9 +33,10 @@ class Team < ApplicationRecord
enum authorize: {common: 0, read: 1, write: 2, admin: 3, owner: 4}
def self.build(organization_id, name, description, authorize, includes_all_project, can_create_org_project)
def self.build(organization_id, name, nickname, description, authorize, includes_all_project, can_create_org_project)
self.create!(organization_id: organization_id,
name: name,
nickname: nickname,
description: description,
authorize: authorize,
includes_all_project: includes_all_project,

View File

@ -146,9 +146,14 @@ 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 :members, dependent: :destroy
has_many :team_users, dependent: :destroy
has_many :teams, through: :team_users
# 教学案例
# has_many :libraries, dependent: :destroy

View File

@ -18,7 +18,9 @@ class Projects::ListMyQuery < ApplicationQuery
end
if params[:category].blank?
projects = projects.members_projects(user.id)
normal_projects = projects.members_projects(user.id).to_sql
org_projects = projects.joins(team_projects: [team: :team_users]).where(team_users: {user_id: user.id}).to_sql
projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
elsif params[:category].to_s == "join"
normal_projects = projects.where.not(user_id: user.id).members_projects(user.id).to_sql
org_projects = projects.joins(team_projects: [team: :team_users]).where(team_users: {user_id: user.id}).to_sql

View File

@ -0,0 +1,148 @@
class Gitea::Accelerator::MigrateService < ApplicationService
attr_reader :params
# params description:
# {
# auth_username string
# clone_addr* string #clone地址
# description string
# issues boolean
# labels boolean
# milestones boolean
# mirror boolean
# private boolean
# pull_requests boolean
# releases boolean
# repo_name* string #仓库名称
# uid* integer($int64) #gitea用户id或组织id
# wiki boolean
# }
# EX:
# params = {
# clone_addr: 'xxx.com',
# repo_name: 'repo_name',
# uid: 2,
# private: false
# }
def initialize(params)
@params = params
end
def call
return error('[gitea:] accelerator config missing') if check_accelerator!
response = post(url, request_params)
render_status(response)
end
private
def request_params
{
uid: access_uid,
clone_addr: params[:clone_addr],
repo_name: params[:repository_name],
auth_username: params[:auth_username],
auth_password: params[:auth_password],
mirror: ActiveModel::Type::Boolean.new.cast(params[:is_mirror])
}
end
def url
"/repos/migrate".freeze
end
def post(url, params)
puts "[gitea] request params: #{params}"
puts "[gitea] access_username: #{access_username}"
puts "[gitea] access_password: #{access_password}"
conn.post do |req|
req.url full_url(url)
req.body = params.to_json
end
end
def conn
@client ||= begin
Faraday.new(url: domain) do |req|
req.request :url_encoded
req.headers['Content-Type'] = 'application/json'
req.response :logger # 显示日志
req.adapter Faraday.default_adapter
req.basic_auth(access_username, access_password)
end
end
@client
end
def base_url
accelerator["base_url"]
end
def domain
accelerator["domain"]
end
def api_url
[domain, base_url].join('')
end
def full_url(api_rest, action='post')
url = [api_url, api_rest].join('').freeze
url = action === 'get' ? url : URI.escape(url)
puts "[gitea] request url: #{url}"
url
end
def access_username
accelerator["access_key_id"]
end
def access_password
accelerator["access_key_secret"]
end
def access_uid
accelerator["access_admin_uid"]
end
def accelerator
Gitea.gitea_config[:accelerator]
end
def render_status(response)
puts "[gitea] response status: #{response.status}"
puts "[gitea] response body: #{response.body}"
case response.status
when 201
success
when 403
error('APIForbiddenError')
when 422
error('APIValidationError')
else
error("MigrateError")
end
end
def error(message)
{
status: :error,
message: message,
data: nil
}
end
def success(data=nil)
{
status: :success,
message: nil,
data: data
}
end
def check_accelerator!
accelerator.blank? || access_username.blank? || access_password.blank? || domain.blank?
end
end

View File

@ -176,6 +176,25 @@ class Gitea::ClientService < ApplicationService
[status, message, body]
end
def render_gitea_response(response)
status = response.status
body = response&.body
log_error(status, body)
message = nil
begin
translate = YAML.load(File.read('config/gitea_response.yml'))
self.class.to_s.underscore.split("/").map{|i| translate=translate[i]}
message = body.nil? ? translate[status]['default'] : JSON.parse(body)['message']
message = translate[status][message].nil? ? message : translate[status][message]
return [status, message]
rescue
return [status, message]
end
end
def get_body_by_status(status, body)
body, message =
case status

View File

@ -20,7 +20,7 @@ class Gitea::PullRequest::MergeService < Gitea::ClientService
def call
response = post(url, request_params)
render_200_no_body(response)
render_gitea_response(response)
end
private

View File

@ -54,7 +54,7 @@ class Organizations::CreateService < ApplicationService
end
def create_owner_info
@owner_team = Team.build(organization.id, "Owners", "", 4, true, true)
@owner_team = Team.build(organization.id, "Owners", "Owner团队", "", 4, true, true)
TeamUnit.unit_types.keys.each do |u_type|
TeamUnit.build(organization.id, owner_team.id, u_type)
end

View File

@ -28,6 +28,10 @@ class Organizations::Teams::CreateService < ApplicationService
params[:name]
end
def nickname
params[:nickname]
end
def description
params[:description]
end
@ -45,7 +49,7 @@ class Organizations::Teams::CreateService < ApplicationService
end
def create_team
@team = Team.build(org.id, name, description, authorize,
@team = Team.build(org.id, name, nickname, description, authorize,
includes_all_project, can_create_org_project)
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,6 @@
class Projects::MigrateService < ApplicationService
attr_reader :user, :params
attr_accessor :project
def initialize(user, params)
@user = user
@ -9,8 +10,9 @@ class Projects::MigrateService < ApplicationService
def call
@project = Project.new(project_params)
if @project.save!
ProjectUnit.init_types(@project.id)
ProjectUnit.init_types(@project.id, project.project_type)
Project.update_mirror_projects_count!
@project.set_owner_permission(user)
Repositories::MigrateService.new(user, @project, repository_params).call
else
#
@ -48,7 +50,8 @@ class Projects::MigrateService < ApplicationService
user_id: params[:user_id],
login: params[:auth_username],
password: params[:auth_password],
is_mirror: params[:is_mirror]
is_mirror: params[:is_mirror],
source_clone_url: params[:source_clone_url]
}
end

View File

@ -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

View File

@ -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
@ -32,9 +33,8 @@ class Projects::TransferService < ApplicationService
def update_visit_teams
if new_owner.is_a?(Organization)
new_owner.teams.where(includes_all_project: true).each do |team|
TeamProject.build(new_owner.id, team.id, project.id)
end
# 为包含组织所有项目的团队创建项目访问权限
new_owner.build_permit_team_projects(project.id)
else
project.team_projects.each(&:destroy!)
end

View File

@ -1,6 +1,6 @@
class PullRequests::MergeService < ApplicationService
attr_reader :owner, :repo, :pull, :current_user, :params
attr_accessor :status, :message
# eq:
# PullRequests::MergeService.call(owner, repo, pull, current_user, params)
def initialize(owner, repo, pull, current_user, params)
@ -15,6 +15,7 @@ class PullRequests::MergeService < ApplicationService
ActiveRecord::Base.transaction do
gitea_pull_merge!
end
self
end
private
@ -22,8 +23,7 @@ class PullRequests::MergeService < ApplicationService
def gitea_pull_merge!
result = Gitea::PullRequest::MergeService.call(@current_user.gitea_token, @owner.login,
@repo.identifier, @pull.gpid, gitea_merge_pull_params)
result[:status] === 200 ? true : false
@status, @message = result
end
def gitea_merge_pull_params

View File

@ -15,6 +15,7 @@ class Repositories::CreateService < ApplicationService
create_gitea_repository
sync_project
sync_repository
@project.set_owner_permission(user)
# if project.project_type == "common"
# chain_params = {
# type: "create",
@ -44,17 +45,7 @@ class Repositories::CreateService < ApplicationService
@gitea_repository = Gitea::Repository::CreateService.new(user.gitea_token, gitea_repository_params).call
elsif project.owner.is_a?(Organization)
@gitea_repository = Gitea::Organization::Repository::CreateService.call(user.gitea_token, project.owner.login, gitea_repository_params)
project.owner.teams.each do |team|
next unless team.includes_all_project
TeamProject.build(project.user_id, team.id, project.id)
end
create_manager_member
end
end
def create_manager_member
return if project.owner.is_owner?(user.id)
project.add_member!(user.id, "Manager")
end
def sync_project

View File

@ -21,7 +21,7 @@ class Repositories::MigrateService < ApplicationService
private
def repository_params
params.merge(project_id: project.id, identifier: params[:identifier])
params.merge(project_id: project.id)
end
def gitea_repository_params

View File

@ -23,7 +23,7 @@
<td><%= list_index_no((params[:page] || 1).to_i, index) %></td>
<td><%= project.id %></td>
<td class="text-left">
<%= link_to(project.name, "/projects/#{project.id}", target: '_blank') %>
<%= link_to(project.name, "/projects/#{project&.owner&.login}/#{project.identifier}", target: '_blank') %>
</td>
<td><%= project.is_public ? '√' : '' %></td>
<td><%= project.issues.size %></td>
@ -33,7 +33,7 @@
<td><%= project.versions.size %></td>
<td><%= project.members.size %></td>
<td>
<%= project.owner ? link_to(project.owner&.real_name, "/users/#{project.owner&.login}", target: '_blank') : "" %>
<%= link_to_project(project) %>
</td>
<td><%= project.created_on&.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">

View File

@ -4,7 +4,7 @@ json.commits do
json.array! @compare_result['Commits'] do |commit|
json.author do
# TODO: 获取头像地址待优化
forge_user = User.includes(:user_extension).select(:id, :login).find_by(login: commit['Author']['Name'])
forge_user = User.includes(:user_extension).find_by(login: commit['Author']['Name'])
json.login commit['Author']['Name']
json.name commit['Author']['Name']
json.image_url forge_user.nil? ? '' : url_to_avatar(forge_user)
@ -12,7 +12,7 @@ json.commits do
json.committer do
# TODO: 获取头像地址待优化
forge_user = User.includes(:user_extension).select(:id, :login).find_by(login: commit['Committer']['Name'])
forge_user = User.includes(:user_extension).find_by(login: commit['Committer']['Name'])
json.login commit['Committer']['Name']
json.name commit['Committer']['Name']
json.image_url forge_user.nil? ? '' : url_to_avatar(forge_user)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1 @@
json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project}

View File

@ -0,0 +1 @@
json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project}

View File

@ -0,0 +1,4 @@
json.total_count @organizations.size
json.organizations @organizations do |org|
json.partial! "/organizations/organizations/simple", locals: {organization: org}
end

View File

@ -5,10 +5,10 @@ json.issue_priories @project_priories
json.project_author @project.owner.try(:show_real_name)
json.project_name @project.try(:name)
json.members do
json.array! @project_members.to_a.each do |member|
json.id member.user_id
json.login member.user.try(:login)
json.name member.user.try(:show_real_name)
json.avatar_url url_to_avatar(member.user)
json.array! @project_members.to_a.each do |user|
json.id user.id
json.login user.try(:login)
json.name user.try(:show_real_name)
json.avatar_url url_to_avatar(user)
end
end

View File

@ -21,7 +21,7 @@ json.versions_count @project.versions_count #里程碑数量
json.version_releases_count @project.releases_size(@user.try(:id), "all")
json.version_releasesed_count @project.releases_size(@user.try(:id), "released") #已发行的版本
json.permission render_permission(@user, @project)
json.mirror_url @project&.repository.mirror_url
json.mirror_url @project&.repository.remote_mirror_url
json.mirror @project&.repository.mirror_url.present?
json.type @project.numerical_for_project_type
json.open_devops @project.open_devops?
@ -80,7 +80,7 @@ json.contributors do
total_count = @result[:contributor].size
json.list @result[:contributor].each do |contributor|
user = User.find_by(gitea_uid: contributor["id"])
if contributor["login"] == "root"
if contributor["login"] == "root" || user.nil?
total_count -= 1
next
end

View File

@ -8,3 +8,8 @@ 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.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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1 @@
json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project}

View File

@ -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

View File

@ -0,0 +1 @@
json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project}

View File

@ -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

View File

@ -50,9 +50,15 @@ default: &default
redirect_uri: 'https://testforgeplus.trustie.net/api/auth/educoder/callback'
gitea:
access_key_id: 'root'
access_key_secret: '_Trustie_10010'
domain: 'https://testgitea.trustie.net'
access_key_id: ''
access_key_secret: ''
domain: 'https://testgit.trustie.net'
base_url: '/api/v1'
accelerator:
access_key_id: ''
access_key_secret: ''
access_admin_uid: 1
domain: 'https://testgit.trustie.net'
base_url: '/api/v1'
blockchain:
api_url: "blockchain api url"

View File

@ -0,0 +1,7 @@
gitea:
pull_request:
merge_service:
405:
default: "此合并请求有变更与目标分支冲突。"
'User not allowed to merge PR': "用户没有合并请求的权限"
403:

View File

@ -0,0 +1,7 @@
'zh-CN':
activemodel:
attributes:
issues/create_form:
subject: 标题
issues/update_form:
subject: 标题

View File

@ -0,0 +1,9 @@
'zh-CN':
activemodel:
attributes:
organizations/create_form:
name: 组织账号
nickname: 组织名称
location: 组织所在地区
description: 组织简介
visibility: 组织可见性

View File

@ -0,0 +1,7 @@
'zh-CN':
activemodel:
attributes:
organizations/create_team_form:
name: 团队标识
nickname: 团队名称
description: 团队描述

View File

@ -0,0 +1,7 @@
'zh-CN':
activemodel:
attributes:
projects/create_form:
name: 项目名称
repository_name: 仓库名称
description: 项目简介

View File

@ -0,0 +1,8 @@
'zh-CN':
activemodel:
attributes:
projects/update_form:
name: 项目名称
description: 项目简介
project_category_id: 项目类别
project_language_id: 项目语言

View File

@ -0,0 +1,6 @@
'zh-CN':
activemodel:
attributes:
users/login_form:
login: 用户名
password: 密码

View File

@ -270,6 +270,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]
@ -399,7 +406,6 @@ Rails.application.routes.draw do
get :files
get :detail
get :archive
get :top_counts
get :entries
match :sub_entries, :via => [:get, :put]
get :commits
@ -546,6 +552,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',

View File

@ -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

View File

@ -0,0 +1,5 @@
class AddAcceleratorUrlToRepositories < ActiveRecord::Migration[5.2]
def change
add_column :repositories, :accelerator_url, :string, default: ""
end
end

View File

@ -0,0 +1,5 @@
class AddSourceCloneUrlToRepositories < ActiveRecord::Migration[5.2]
def change
add_column :repositories, :source_clone_url, :string, default: ""
end
end

View File

@ -0,0 +1,8 @@
namespace :sync_org_project_permission do
desc "sync organization project team permissions"
task mirror: :environment do
Project.mirror.includes(:team_projects,:owner).where(team_projects: {id: nil}, users: {type: 'Organization'}).find_each do |project|
project.set_owner_permission(nil)
end
end
end

File diff suppressed because it is too large Load Diff