diff --git a/Gemfile.lock b/Gemfile.lock
index 99f40ad94..e27c504aa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -106,7 +106,7 @@ GEM
activerecord (>= 3.1.0, < 7)
diff-lcs (1.3)
diffy (3.3.0)
- doorkeeper (5.5.4)
+ doorkeeper (5.5.1)
railties (>= 5)
doorkeeper-jwt (0.4.1)
jwt (>= 2.1)
diff --git a/app/controllers/projects/project_invite_links_controller.rb b/app/controllers/projects/project_invite_links_controller.rb
new file mode 100644
index 000000000..14bdc959f
--- /dev/null
+++ b/app/controllers/projects/project_invite_links_controller.rb
@@ -0,0 +1,42 @@
+class Projects::ProjectInviteLinksController < Projects::BaseController
+ before_action :require_manager!, except: [:show_link, :redirect_link]
+ before_action :require_login
+
+ def current_link
+ role = params[:role]
+ is_apply = params[:is_apply]
+ return render_error('请输入正确的参数!') unless role.present? && is_apply.present?
+ @project_invite_link = ProjectInviteLink.find_by(user_id: current_user.id, project_id: @project.id, role: role, is_apply: is_apply)
+ @project_invite_link = ProjectInviteLink.build!(@project, current_user, role, is_apply) unless @project_invite_link.present?
+ end
+
+ def generate_link
+ ActiveRecord::Base.transaction do
+ params_data = link_params.merge({user_id: current_user.id, project_id: @project.id})
+ Projects::ProjectInviteLinks::CreateForm.new(params_data).validate!
+ @project_invite_link = ProjectInviteLink.build!(project, user, params_data[:role], params_data[:is_apply])
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+ def show_link
+ @project_invite_link = ProjectInviteLink.find_by(sign: params[:invite_sign])
+ return render_not_found unless @project_invite_link.present?
+ end
+
+ def redirect_link
+ Projects::LinkJoinService.call(current_user, @project, params[:invite_sign])
+ render_ok
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+
+
+ private
+ def link_params
+ params.require(:project_invite_link).permit(:role, :is_apply)
+ end
+end
diff --git a/app/docs/slate/source/includes/_projects.md b/app/docs/slate/source/includes/_projects.md
index e133bcdda..906982b35 100644
--- a/app/docs/slate/source/includes/_projects.md
+++ b/app/docs/slate/source/includes/_projects.md
@@ -1,4 +1,278 @@
# Projects
+## 获取项目邀请链接(项目管理员)
+当前登录(管理员)用户获取项目邀请链接的接口(第一次请求会默认生成role类型为developer和is_apply为true的链接)
+
+> 示例:
+
+```shell
+curl -X GET http://localhost:3000/api/yystopf/kellect/project_invite_links/current_link.json
+```
+
+```javascript
+await octokit.request('GET /api/yystopf/kellect/project_invite_links/current_link.json')
+```
+
+### HTTP 请求
+`GET /api/:owner/:repo/project_invite_links/current_link.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+|role |是| |string |项目权限,reporter: 报告者, developer: 开发者,manager:管理员 |
+|is_apply |是| |boolean |是否需要审核 |
+
+### 返回字段说明
+参数 | 类型 | 字段说明
+--------- | ----------- | -----------
+|id |int |链接id |
+|role |string |邀请角色|
+|is_apply |boolean |是否需要审核 |
+|sign |string |邀请标识(放在链接后面即可)|
+|expired_at |string |链接过期时间|
+|user.id |int |链接创建者的id |
+|user.type |string |链接创建者的类型 |
+|user.name |string |链接创建者的名称 |
+|user.login |string |链接创建者的标识 |
+|user.image_url |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 |链接关联项目拥有者头像 |
+
+> 返回的JSON示例:
+
+```json
+{
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
+ "user": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ },
+ "project": {
+ "id": 474,
+ "identifier": "kellect",
+ "name": "kellect",
+ "description": null,
+ "is_public": true,
+ "owner": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ }
+ }
+}
+```
+## 生成项目邀请链接(项目管理员)
+当前登录(管理员)用户生成的项目邀请链接,可选role和is_apply参数
+
+> 示例:
+
+```shell
+curl -X POST http://localhost:3000/api/yystopf/kellect/project_invite_links/generate_link.json
+```
+
+```javascript
+await octokit.request('POST /api/yystopf/kellect/project_invite_links/generate_link.json')
+```
+
+### HTTP 请求
+`POST /api/:owner/:repo/project_invite_links/generate_link.json`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+|role |是| |string |项目权限,reporter: 报告者, developer: 开发者,manager:管理员 |
+|is_apply |是| |boolean |是否需要审核 |
+
+
+> 请求的JSON示例
+
+```json
+{
+ "role": "developer",
+ "is_apply": false
+}
+```
+
+### 返回字段说明
+参数 | 类型 | 字段说明
+--------- | ----------- | -----------
+|id |int |链接id |
+|role |string |邀请角色|
+|is_apply |boolean |是否需要审核 |
+|sign |string |邀请标识(放在链接后面即可)|
+|expired_at |string |链接过期时间|
+|user.id |int |链接创建者的id |
+|user.type |string |链接创建者的类型 |
+|user.name |string |链接创建者的名称 |
+|user.login |string |链接创建者的标识 |
+|user.image_url |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 |链接关联项目拥有者头像 |
+
+> 返回的JSON示例:
+
+```json
+{
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
+ "user": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ },
+ "project": {
+ "id": 474,
+ "identifier": "kellect",
+ "name": "kellect",
+ "description": null,
+ "is_public": true,
+ "owner": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ }
+ }
+}
+```
+
+## 获取邀请链接信息(被邀请用户)
+用户请求邀请链接时,通过该接口来获取链接的信息
+
+> 示例:
+
+```shell
+curl -X GET http://localhost:3000/api/yystopf/kellect/project_invite_links/show_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6
+```
+
+```javascript
+await octokit.request('POST /api/yystopf/kellect/project_invite_links/show_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6')
+```
+
+### HTTP 请求
+`POST /api/:owner/:repo/project_invite_links/show_link.json?invite_sign=xxx`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+|invite_sign |是| |string |项目邀请链接的标识 |
+
+### 返回字段说明
+参数 | 类型 | 字段说明
+--------- | ----------- | -----------
+|id |int |链接id |
+|role |string |邀请角色|
+|is_apply |boolean |是否需要审核 |
+|sign |string |邀请标识(放在链接后面即可)|
+|expired_at |string |链接过期时间|
+|user.id |int |链接创建者的id |
+|user.type |string |链接创建者的类型 |
+|user.name |string |链接创建者的名称 |
+|user.login |string |链接创建者的标识 |
+|user.image_url |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 |链接关联项目拥有者头像 |
+
+> 返回的JSON示例:
+
+```json
+{
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
+ "user": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ },
+ "project": {
+ "id": 474,
+ "identifier": "kellect",
+ "name": "kellect",
+ "description": null,
+ "is_public": true,
+ "owner": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ }
+ }
+}
+```
+
+## 接受项目邀请链接(被邀请用户)
+当前登录(非项目)用户加入项目的接口,如果项目链接不需要审核,请求成功后即加入项目,如果需要审核,那么会提交一个申请,需要项目管理员审核
+
+> 示例:
+
+```shell
+curl -X POST http://localhost:3000/api/yystopf/kellect/project_invite_links/redirect_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6
+```
+
+```javascript
+await octokit.request('POST /api/yystopf/kellect/project_invite_links/redirect_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6')
+```
+
+### HTTP 请求
+`POST /api/:owner/:repo/project_invite_links/redirect_link.json?invite_sign=xxx`
+
+### 请求参数
+参数 | 必选 | 默认 | 类型 | 字段说明
+--------- | ------- | ------- | -------- | ----------
+|invite_sign |是| |string |项目邀请链接的标识 |
+
+> 返回的JSON示例:
+
+```json
+{
+ "status": 0,
+ "message": "success"
+}
+```
## 申请加入项目
申请加入项目
diff --git a/app/forms/projects/project_invite_links/create_form.rb b/app/forms/projects/project_invite_links/create_form.rb
new file mode 100644
index 000000000..701625c0a
--- /dev/null
+++ b/app/forms/projects/project_invite_links/create_form.rb
@@ -0,0 +1,8 @@
+class Projects::ProjectInviteLinks::CreateForm < BaseForm
+ attr_accessor :user_id, :project_id, :role, :is_apply
+
+ validates :user_id, :project_id, :role, presence: true
+ validates :role, inclusion: { in: %w(manager developer reporter), message: "请输入正确的权限." }
+ validates :is_apply, inclusion: {in: [true, false], message: "请输入是否需要管理员审核."}
+end
+
\ No newline at end of file
diff --git a/app/models/applied_project.rb b/app/models/applied_project.rb
index ea7ca6eee..2b18d4d82 100644
--- a/app/models/applied_project.rb
+++ b/app/models/applied_project.rb
@@ -2,24 +2,27 @@
#
# Table name: forge_applied_projects
#
-# id :integer not null, primary key
-# project_id :integer
-# user_id :integer
-# role :integer default("0")
-# status :integer default("0")
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :integer not null, primary key
+# project_id :integer
+# user_id :integer
+# role :integer default("0")
+# status :integer default("0")
+# created_at :datetime not null
+# updated_at :datetime not null
+# project_invite_link_id :integer
#
# Indexes
#
-# index_forge_applied_projects_on_project_id (project_id)
-# index_forge_applied_projects_on_user_id (user_id)
+# index_forge_applied_projects_on_project_id (project_id)
+# index_forge_applied_projects_on_project_invite_link_id (project_invite_link_id)
+# index_forge_applied_projects_on_user_id (user_id)
#
class AppliedProject < ApplicationRecord
self.table_name = "forge_applied_projects"
belongs_to :user
belongs_to :project
+ belongs_to :project_invite_link, optional: true
has_many :applied_messages, as: :applied, dependent: :destroy
# has_many :forge_activities, as: :forge_act, dependent: :destroy
diff --git a/app/models/project.rb b/app/models/project.rb
index 444a0ec87..31da85252 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -128,6 +128,7 @@ class Project < ApplicationRecord
has_many :pinned_projects, dependent: :destroy
has_many :has_pinned_users, through: :pinned_projects, source: :user
has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
+ has_many :project_invite_links, dependent: :destroy
after_create :incre_user_statistic, :incre_platform_statistic
after_save :check_project_members
before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data
diff --git a/app/models/project_invite_link.rb b/app/models/project_invite_link.rb
new file mode 100644
index 000000000..ea77b9517
--- /dev/null
+++ b/app/models/project_invite_link.rb
@@ -0,0 +1,59 @@
+# == Schema Information
+#
+# Table name: project_invite_links
+#
+# id :integer not null, primary key
+# project_id :integer
+# user_id :integer
+# role :integer default("4")
+# is_apply :boolean default("1")
+# sign :string(255)
+# expired_at :datetime
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_project_invite_links_on_project_id (project_id)
+# index_project_invite_links_on_sign (sign)
+# index_project_invite_links_on_user_id (user_id)
+#
+
+class ProjectInviteLink < ApplicationRecord
+
+ default_scope { where("expired_at > ?", Time.now).or(where(expired_at: nil)) }
+
+ belongs_to :project
+ belongs_to :user
+ has_many :applied_projects
+
+ scope :with_project_id, -> (project_id) {where(project_id: project_id)}
+ scope :with_user_id, -> (user_id) {where(user_id: user_id)}
+
+ enum role: {manager: 3, developer: 4, reporter: 5}
+
+ before_create :set_old_data_expired_at
+
+ def self.random_hex_sign
+ hex = (SecureRandom.hex(32))
+ return hex unless ProjectInviteLink.where(sign: hex).exists?
+ end
+
+ def self.build!(project, user, role="developer", is_apply=true)
+ self.create!(
+ project_id: project&.id,
+ user_id: user&.id,
+ role: role,
+ is_apply: is_apply,
+ sign: random_hex_sign,
+ expired_at: Time.now + 3.days
+ )
+ end
+
+ private
+ def set_old_data_expired_at
+ ProjectInviteLink.where(user_id: self.user_id, project_id: self.project, role: self.role, is_apply: self.is_apply).update_all(expired_at: Time.now)
+ end
+
+
+end
diff --git a/app/services/projects/link_join_service.rb b/app/services/projects/link_join_service.rb
new file mode 100644
index 000000000..e586933f9
--- /dev/null
+++ b/app/services/projects/link_join_service.rb
@@ -0,0 +1,76 @@
+class Projects::LinkJoinService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :project, :invite_sign, :params
+
+ def initialize(user, project, invite_sign, params={})
+ @user = user
+ @project = project
+ @invite_sign = invite_sign
+ @params = params
+ end
+
+ def call
+ ActiveRecord::Base.transaction do
+ validate!
+ if invite_link.is_apply
+ # 如果需要申请才能加入,创建一条申请记录
+ create_applied_project!
+ else
+ # 如果不需要申请,直接为项目添加该成员
+ create_member!
+ end
+ end
+ end
+
+ private
+ def validate!
+ raise Error, 'invite_sign必须存在!' if invite_sign.blank?
+ raise Error, '邀请链接不存在!' unless invite_link.present?
+ raise Error, '邀请链接已失效!' unless invite_user_in_project
+ raise Error, '用户已加入该项目!' if project.member?(user.id)
+ end
+
+ def create_applied_project!
+ user.applied_projects.create!(project: project, role: role_value, project_invite_link_id: invite_link&.id)
+ end
+
+ def create_member!
+ Projects::AddMemberInteractor.call(project.owner, project, user, permission)
+ end
+
+ def invite_link
+ ProjectInviteLink.find_by(project_id: project.id, sign: invite_sign)
+ end
+
+ def invite_user_in_project
+ in_project = project.member?(invite_link.user)
+ invite_link.update_column(:expired_at, Time.now) unless in_project
+ in_project
+ end
+
+ def role_value
+ @_role ||=
+ case invite_link&.role
+ when 'manager' then 3
+ when 'developer' then 4
+ when 'reporter' then 5
+ else
+ 5
+ end
+ end
+
+ def permission
+ case invite_link&.role
+ when 'manager'
+ 'admin'
+ when 'developer'
+ 'write'
+ when 'reporter'
+ 'read'
+ else
+ 'read'
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/views/projects/_detail.json.jbuilder b/app/views/projects/_detail.json.jbuilder
new file mode 100644
index 000000000..ed23fdef2
--- /dev/null
+++ b/app/views/projects/_detail.json.jbuilder
@@ -0,0 +1,8 @@
+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
\ No newline at end of file
diff --git a/app/views/projects/project_invite_links/_detail.json.jbuilder b/app/views/projects/project_invite_links/_detail.json.jbuilder
new file mode 100644
index 000000000..c8e840d0e
--- /dev/null
+++ b/app/views/projects/project_invite_links/_detail.json.jbuilder
@@ -0,0 +1,12 @@
+json.(project_invite_link, :id, :role, :is_apply, :sign)
+json.expired_at format_time(project_invite_link&.expired_at)
+json.user do
+ json.partial! "/users/user_simple", locals: {user: project_invite_link.user}
+end
+if project_invite_link&.project.present?
+ json.project do
+ json.partial! "/projects/detail", locals: {project: project_invite_link.project}
+ end
+else
+ json.project nil
+end
diff --git a/app/views/projects/project_invite_links/current_link.json.jbuilder b/app/views/projects/project_invite_links/current_link.json.jbuilder
new file mode 100644
index 000000000..1903e10a9
--- /dev/null
+++ b/app/views/projects/project_invite_links/current_link.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'detail', locals: { project_invite_link: @project_invite_link }
diff --git a/app/views/projects/project_invite_links/generate_link.json.jbuilder b/app/views/projects/project_invite_links/generate_link.json.jbuilder
new file mode 100644
index 000000000..1903e10a9
--- /dev/null
+++ b/app/views/projects/project_invite_links/generate_link.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'detail', locals: { project_invite_link: @project_invite_link }
diff --git a/app/views/projects/project_invite_links/show_link.json.jbuilder b/app/views/projects/project_invite_links/show_link.json.jbuilder
new file mode 100644
index 000000000..1903e10a9
--- /dev/null
+++ b/app/views/projects/project_invite_links/show_link.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'detail', locals: { project_invite_link: @project_invite_link }
diff --git a/config/routes.rb b/config/routes.rb
index 4b434f779..d3b2e7f36 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -604,6 +604,14 @@ Rails.application.routes.draw do
post :cancel
end
end
+ resources :project_invite_links, only: [:index] do
+ collection do
+ get :current_link
+ post :generate_link
+ get :show_link
+ post :redirect_link
+ end
+ end
resources :webhooks, except: [:show, :new] do
member do
get :tasks
diff --git a/db/migrate/20220614070028_create_project_invite_links.rb b/db/migrate/20220614070028_create_project_invite_links.rb
new file mode 100644
index 000000000..5b482967f
--- /dev/null
+++ b/db/migrate/20220614070028_create_project_invite_links.rb
@@ -0,0 +1,16 @@
+class CreateProjectInviteLinks < ActiveRecord::Migration[5.2]
+ def change
+ create_table :project_invite_links do |t|
+ t.references :project
+ t.references :user
+ t.integer :role, default: 4
+ t.boolean :is_apply, default: true
+ t.string :sign
+ t.datetime :expired_at
+
+ t.timestamps
+ end
+
+ add_index :project_invite_links, :sign
+ end
+end
diff --git a/db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb b/db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb
new file mode 100644
index 000000000..31cbd9512
--- /dev/null
+++ b/db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb
@@ -0,0 +1,6 @@
+class AddProjectInviteLinkToAppliedProjects < ActiveRecord::Migration[5.2]
+ def change
+ add_column :forge_applied_projects, :project_invite_link_id, :integer
+ add_index :forge_applied_projects, :project_invite_link_id
+ end
+end
diff --git a/db/migrate/20220616040105_create_doorkeeper_tables.rb b/db/migrate/20220616040105_create_doorkeeper_tables.rb
index b4ec636ed..890b31d80 100644
--- a/db/migrate/20220616040105_create_doorkeeper_tables.rb
+++ b/db/migrate/20220616040105_create_doorkeeper_tables.rb
@@ -48,8 +48,8 @@ class CreateDoorkeeperTables < ActiveRecord::Migration[5.2]
# characters. More info on custom token generators in:
# https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
#
- # t.text :token, null: false
- t.string :token, null: false
+ t.text :token, null: false
+ # t.string :token, null: false
t.string :refresh_token
t.integer :expires_in
@@ -73,7 +73,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration[5.2]
t.string :previous_refresh_token, null: false, default: ""
end
- add_index :oauth_access_tokens, :token, unique: true
+ # add_index :oauth_access_tokens, :token, unique: true
add_index :oauth_access_tokens, :refresh_token, unique: true
add_foreign_key(
:oauth_access_tokens,
diff --git a/db/migrate/20220617103002_change_oauth_access_tokens_token_column_length.rb b/db/migrate/20220617103002_change_oauth_access_tokens_token_column_length.rb
deleted file mode 100644
index 9c78ac285..000000000
--- a/db/migrate/20220617103002_change_oauth_access_tokens_token_column_length.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class ChangeOauthAccessTokensTokenColumnLength < ActiveRecord::Migration[5.2]
- def change
- change_column :oauth_access_tokens, :token, :string, limit: 500
- end
-end
diff --git a/public/docs/api.html b/public/docs/api.html
index 8ef6924b9..82b751e9f 100644
--- a/public/docs/api.html
+++ b/public/docs/api.html
@@ -425,6 +425,18 @@
Projects
+ -
+ 获取项目邀请链接(项目管理员)
+
+ -
+ 生成项目邀请链接(项目管理员)
+
+ -
+ 获取邀请链接信息(被邀请用户)
+
+ -
+ 接受项目邀请链接(被邀请用户)
+
-
申请加入项目
@@ -4390,7 +4402,572 @@ Success — a happy kitten is an authenticated kitten!
"created_at": "2021-06-09 16:41",
"time_ago": "7分钟前"
}
-Projects
申请加入项目
+Projects
获取项目邀请链接(项目管理员)
+当前登录(管理员)用户获取项目邀请链接的接口(第一次请求会默认生成role类型为developer和is_apply为true的链接)
+
+
+示例:
+
+curl -X GET http://localhost:3000/api/yystopf/kellect/project_invite_links/current_link.json
+
await octokit.request('GET /api/yystopf/kellect/project_invite_links/current_link.json')
+
HTTP 请求
+GET /api/:owner/:repo/project_invite_links/current_link.json
+请求参数
+
+
+参数 |
+必选 |
+默认 |
+类型 |
+字段说明 |
+
+
+
+role |
+是 |
+ |
+string |
+项目权限,reporter: 报告者, developer: 开发者,manager:管理员 |
+
+
+is_apply |
+是 |
+ |
+boolean |
+是否需要审核 |
+
+
+返回字段说明
+
+
+参数 |
+类型 |
+字段说明 |
+
+
+
+id |
+int |
+链接id |
+
+
+role |
+string |
+邀请角色 |
+
+
+is_apply |
+boolean |
+是否需要审核 |
+
+
+sign |
+string |
+邀请标识(放在链接后面即可) |
+
+
+expired_at |
+string |
+链接过期时间 |
+
+
+user.id |
+int |
+链接创建者的id |
+
+
+user.type |
+string |
+链接创建者的类型 |
+
+
+user.name |
+string |
+链接创建者的名称 |
+
+
+user.login |
+string |
+链接创建者的标识 |
+
+
+user.image_url |
+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 |
+链接关联项目拥有者头像 |
+
+
+
+
+返回的JSON示例:
+
+{
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
+ "user": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ },
+ "project": {
+ "id": 474,
+ "identifier": "kellect",
+ "name": "kellect",
+ "description": null,
+ "is_public": true,
+ "owner": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ }
+ }
+}
+
生成项目邀请链接(项目管理员)
+当前登录(管理员)用户生成的项目邀请链接,可选role和is_apply参数
+
+
+示例:
+
+curl -X POST http://localhost:3000/api/yystopf/kellect/project_invite_links/generate_link.json
+
await octokit.request('POST /api/yystopf/kellect/project_invite_links/generate_link.json')
+
HTTP 请求
+POST /api/:owner/:repo/project_invite_links/generate_link.json
+请求参数
+
+
+参数 |
+必选 |
+默认 |
+类型 |
+字段说明 |
+
+
+
+role |
+是 |
+ |
+string |
+项目权限,reporter: 报告者, developer: 开发者,manager:管理员 |
+
+
+is_apply |
+是 |
+ |
+boolean |
+是否需要审核 |
+
+
+
+
+请求的JSON示例
+
+{
+ "role": "developer",
+ "is_apply": false
+}
+
返回字段说明
+
+
+参数 |
+类型 |
+字段说明 |
+
+
+
+id |
+int |
+链接id |
+
+
+role |
+string |
+邀请角色 |
+
+
+is_apply |
+boolean |
+是否需要审核 |
+
+
+sign |
+string |
+邀请标识(放在链接后面即可) |
+
+
+expired_at |
+string |
+链接过期时间 |
+
+
+user.id |
+int |
+链接创建者的id |
+
+
+user.type |
+string |
+链接创建者的类型 |
+
+
+user.name |
+string |
+链接创建者的名称 |
+
+
+user.login |
+string |
+链接创建者的标识 |
+
+
+user.image_url |
+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 |
+链接关联项目拥有者头像 |
+
+
+
+
+返回的JSON示例:
+
+{
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
+ "user": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ },
+ "project": {
+ "id": 474,
+ "identifier": "kellect",
+ "name": "kellect",
+ "description": null,
+ "is_public": true,
+ "owner": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ }
+ }
+}
+
获取邀请链接信息(被邀请用户)
+用户请求邀请链接时,通过该接口来获取链接的信息
+
+
+示例:
+
+curl -X GET http://localhost:3000/api/yystopf/kellect/project_invite_links/show_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6
+
await octokit.request('POST /api/yystopf/kellect/project_invite_links/show_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6')
+
HTTP 请求
+POST /api/:owner/:repo/project_invite_links/show_link.json?invite_sign=xxx
+请求参数
+
+
+参数 |
+必选 |
+默认 |
+类型 |
+字段说明 |
+
+
+
+invite_sign |
+是 |
+ |
+string |
+项目邀请链接的标识 |
+
+
+返回字段说明
+
+
+参数 |
+类型 |
+字段说明 |
+
+
+
+id |
+int |
+链接id |
+
+
+role |
+string |
+邀请角色 |
+
+
+is_apply |
+boolean |
+是否需要审核 |
+
+
+sign |
+string |
+邀请标识(放在链接后面即可) |
+
+
+expired_at |
+string |
+链接过期时间 |
+
+
+user.id |
+int |
+链接创建者的id |
+
+
+user.type |
+string |
+链接创建者的类型 |
+
+
+user.name |
+string |
+链接创建者的名称 |
+
+
+user.login |
+string |
+链接创建者的标识 |
+
+
+user.image_url |
+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 |
+链接关联项目拥有者头像 |
+
+
+
+
+返回的JSON示例:
+
+{
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
+ "user": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ },
+ "project": {
+ "id": 474,
+ "identifier": "kellect",
+ "name": "kellect",
+ "description": null,
+ "is_public": true,
+ "owner": {
+ "id": 2,
+ "type": "User",
+ "name": "heh",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
+ }
+ }
+}
+
接受项目邀请链接(被邀请用户)
+当前登录(非项目)用户加入项目的接口,如果项目链接不需要审核,请求成功后即加入项目,如果需要审核,那么会提交一个申请,需要项目管理员审核
+
+
+示例:
+
+curl -X POST http://localhost:3000/api/yystopf/kellect/project_invite_links/redirect_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6
+
await octokit.request('POST /api/yystopf/kellect/project_invite_links/redirect_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6')
+
HTTP 请求
+POST /api/:owner/:repo/project_invite_links/redirect_link.json?invite_sign=xxx
+请求参数
+
+
+参数 |
+必选 |
+默认 |
+类型 |
+字段说明 |
+
+
+
+invite_sign |
+是 |
+ |
+string |
+项目邀请链接的标识 |
+
+
+
+
+返回的JSON示例:
+
+{
+ "status": 0,
+ "message": "success"
+}
+
申请加入项目
申请加入项目
@@ -4398,9 +4975,9 @@ Success — a happy kitten is an authenticated kitten!
curl -X POST http://localhost:3000/api/applied_projects.json
await octokit.request('POST /api/appliedr_projects.json')
-
HTTP 请求
+HTTP 请求
POST /api/applied_projects.json
-请求参数
+请求参数
参数 |
@@ -4435,7 +5012,7 @@ Success — a happy kitten is an authenticated kitten!
"role": "developer"
}
}
-返回字段说明
+返回字段说明
参数 |
@@ -4576,9 +5153,9 @@ Success — a happy kitten is an authenticated kitten!
-d "limit=5" \
http://localhost:3000/api/projects | jq
await octokit.request('GET /api/projects')
-
HTTP 请求
+HTTP 请求
GET api/projects
-请求参数
+请求参数
参数 |
@@ -4645,7 +5222,7 @@ http://localhost:3000/api/projects | jq
项目类型, 取值为:common、mirror; common:开源托管项目, mirror:开源镜像项目 |
-返回字段说明
+返回字段说明
参数 |
@@ -4797,9 +5374,9 @@ Remember — a happy kitten is an authenticated kitten!
curl -X GET \
http://localhost:3000/api/projects/recommend | jq
await octokit.request('GET /api/projects/recommend.json')
-
HTTP 请求
+HTTP 请求
GET api/projects/recommend
-返回字段说明
+返回字段说明
参数 |
@@ -4933,9 +5510,9 @@ Remember — a happy kitten is an authenticated kitten!
curl -X GET \
http://localhost:3000/api/yystopf/ceshi/menu_list | jq
await octokit.request('GET /api/yystopf/ceshi/menu_list')
-
HTTP 请求
+HTTP 请求
GET api/:owner/:repo/menu_list
-请求参数
+请求参数
参数 |
@@ -4960,7 +5537,7 @@ http://localhost:3000/api/yystopf/ceshi/menu_list | jq
项目标识identifier |
-返回字段说明
+返回字段说明
参数 |
@@ -5001,9 +5578,9 @@ http://localhost:3000/api/yystopf/ceshi/menu_list | jq
curl -X GET \
http://localhost:3000/api/jasder/forgeplus/about | jq
await octokit.request('GET /api/jasder/forgeplus/about')
-
HTTP 请求
+HTTP 请求
GET api/:owner/:repo/about
-请求参数
+请求参数
参数 |
@@ -5028,7 +5605,7 @@ http://localhost:3000/api/jasder/forgeplus/about | jq
项目标识identifier |
-返回字段说明
+返回字段说明
参数 |
@@ -5074,7 +5651,7 @@ Remember — a happy kitten is an authenticated kitten!
curl -X GET \
http://localhost:3000/api/yystopf/ceshi/project_units.json
await octokit.request('GET /api/yystopf/ceshi/project_units')
-
HTTP 请求
+HTTP 请求
GET /api/yystopf/ceshi/project_units
返回字段说明:
@@ -5117,9 +5694,9 @@ http://localhost:3000/api/yystopf/ceshi/project_units.json
-d "{ \"unit_typs\": [\"code\", \"pulls\"]}" \
http://localhost:3000/api/yystopf/ceshi/project_units.json
await octokit.request('POST /api/yystopf/ceshi/project_units')
-
HTTP 请求
+HTTP 请求
POST /api/yystopf/ceshi/project_units
-请求参数
+请求参数
参数 |
@@ -5181,9 +5758,9 @@ http://localhost:3000/api/yystopf/ceshi/project_units.json
-d "license_id=1" \
http://localhost:3000/api/projects.json
await octokit.request('GET /api/projects.json')
-
HTTP 请求
+HTTP 请求
POST api/projects
-请求参数
+请求参数
参数 |
@@ -5257,7 +5834,7 @@ http://localhost:3000/api/projects.json
项目是否私有, true:为私有,false: 公开,默认为公开 |
-返回字段说明
+返回字段说明
参数 |
@@ -5299,9 +5876,9 @@ http://localhost:3000/api/projects.json
-d "project_language_id=2" \
http://localhost:3000/api/projects/migrate.json
await octokit.request('GET /api/projects/migrate.json')
-
HTTP 请求
+HTTP 请求
POST api/projects/migrate.json
-请求参数
+请求参数
参数 |
@@ -5389,7 +5966,7 @@ http://localhost:3000/api/projects/migrate.json
项目是否私有, true:为私有,false: 非私有,默认为公开 |
-返回字段说明
+返回字段说明
参数 |
@@ -5424,9 +6001,9 @@ http://localhost:3000/api/projects/migrate.json
curl -X POST http://localhost:3000/api/repositories/1244/sync_mirror.json
await octokit.request('POST /api/repositories/1244/sync_mirror.json')
-
HTTP 请求
+HTTP 请求
POST api/repositories/:id/sync_mirror.json
-请求参数
+请求参数
参数 |
@@ -5444,7 +6021,7 @@ http://localhost:3000/api/projects/migrate.json
仓库id |
-返回字段说明
+返回字段说明
参数 |
@@ -5479,9 +6056,9 @@ http://localhost:3000/api/projects/migrate.json
curl -X POST http://localhost:3000/api/jasder/forgeplus/forks.json
await octokit.request('POST /api/jaser/jasder_test/forks.json')
-
HTTP 请求
+HTTP 请求
POST api/:owner/:repo/forks.json
-请求参数
+请求参数
参数 |
@@ -5506,7 +6083,7 @@ http://localhost:3000/api/projects/migrate.json
项目标识identifier |
-返回字段说明
+返回字段说明
参数 |
@@ -5542,9 +6119,9 @@ http://localhost:3000/api/projects/migrate.json
curl -X GET \
http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizations.json | jq
await octokit.request('GET /api/:owner/:repo/applied_transfer_projects/organizations')
-
HTTP 请求
+HTTP 请求
GET api/:owner/:repo/applied_transfer_projects/organizations
-请求参数
+请求参数
参数 |
@@ -5569,7 +6146,7 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
项目标识identifier |
-返回字段说明
+返回字段说明
参数 |
@@ -5636,9 +6213,9 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects.json
await octokit.request('POST /api/:owner/:repo/applied_transfer_projects.json')
-
HTTP 请求
+HTTP 请求
POST /api/:owner/:repo/applied_transfer_projects.json
-请求参数
+请求参数
参数 |
@@ -5670,7 +6247,7 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
迁移对象标识 |
-返回字段说明
+返回字段说明
参数 |
@@ -5840,9 +6417,9 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/cancel.json
await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.json')
-
HTTP 请求
+HTTP 请求
POST /api/:owner/:repo/applied_transfer_projects/cancel.json
-请求参数
+请求参数
参数 |
@@ -5867,7 +6444,7 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
项目标识identifier |
-返回字段说明
+返回字段说明
参数 |
@@ -6037,9 +6614,9 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat
curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json
await octokit.request('POST /api/:owner/:repo/quit.json')
-
HTTP 请求
+HTTP 请求
POST /api/:owner/:repo/quit.json
-请求参数
+请求参数
参数 |
diff --git a/spec/models/project_invite_link_spec.rb b/spec/models/project_invite_link_spec.rb
new file mode 100644
index 000000000..d7c46fa5a
--- /dev/null
+++ b/spec/models/project_invite_link_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe ProjectInviteLink, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end