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 @@
申请加入项目
+当前登录(管理员)用户获取项目邀请链接的接口(第一次请求会默认生成role类型为developer和is_apply为true的链接)
-示例:
curl -X POST http://localhost:3000/api/applied_projects.json
-await octokit.request('POST /api/appliedr_projects.json')
+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 请求
-POST /api/applied_projects.json
+GET /api/:owner/:repo/project_invite_links/current_link.json
请求参数
@@ -4411,31 +4423,21 @@ Success — a happy kitten is an authenticated kitten!
-applied_project.code
+role
是
string
-邀请码
+项目权限,reporter: 报告者, developer: 开发者,manager:管理员
-applied_project.role
-否
+is_apply
+是
-string
-项目权限,reporter: 报告者, developer: 开发者,manager:管理员
+boolean
+是否需要审核
-
-
-请求的JSON示例
-
-{
- "applied_project": {
- "code": "1una34",
- "role": "developer"
- }
-}
-
返回字段说明
+返回字段说明
参数
@@ -4446,92 +4448,102 @@ Success — a happy kitten is an authenticated kitten!
id
int
-申请id
+链接id
-status
+role
string
-申请状态,canceled:取消,common:正在申请, accept:已接受,refuse:已拒绝
+邀请角色
-time_ago
+is_apply
+boolean
+是否需要审核
+
+
+sign
string
-项目申请创建的时间
+邀请标识(放在链接后面即可)
-project.id
+expired_at
+string
+链接过期时间
+
+
+user.id
int
-申请项目的id
+链接创建者的id
-project.identifier
+user.type
string
-申请项目的标识
+链接创建者的类型
-project.name
+user.name
string
-申请项目的名称
+链接创建者的名称
-project.description
+user.login
string
-申请项目的描述
+链接创建者的标识
-project.is_public
-bool
-申请项目是否公开
+user.image_url
+string
+链接创建者头像
-project.owner.id
-bool
-申请项目拥有者id
+project.id
+int
+链接关联项目的id
-project.owner.type
+project.identifier
string
-申请项目拥有者类型
+链接关联项目的标识
-project.owner.name
+project.name
string
-申请项目拥有者昵称
+链接关联项目的名称
-project.owner.login
+project.description
string
-申请项目拥有者标识
+链接关联项目的描述
-project.owner.image_url
-string
-申请项目拥有者头像
+project.is_public
+bool
+链接关联项目是否公开
-user.id
-int
-申请创建者的id
+project.owner.id
+bool
+链接关联项目拥有者id
-user.type
+project.owner.type
string
-申请创建者的类型
+链接关联项目拥有者类型
-user.name
+project.owner.name
string
-申请创建者的名称
+链接关联项目拥有者昵称
-user.login
+project.owner.login
string
-申请创建者的标识
+链接关联项目拥有者标识
-user.image_url
+project.owner.image_url
string
-申请创建者头像
+链接关联项目拥有者头像
@@ -4539,45 +4551,43 @@ Success — a happy kitten is an authenticated kitten!
返回的JSON示例:
{
- "project": {
- "id": 74,
- "identifier": "hehuisssjssjjsjs",
- "name": "hehuisssjssjjsjs",
- "description": "wwww",
- "is_public": false,
- "owner": {
- "id": 10,
- "type": "User",
- "name": "testforge1",
- "login": "testforge1",
- "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png"
- }
- },
+ "id": 7,
+ "role": "developer",
+ "is_apply": false,
+ "sign": "6b6b454843c291d4e52e60853cb8ad9f",
+ "expired_at": "2022-06-23 10:08",
"user": {
- "id": 6,
+ "id": 2,
"type": "User",
- "name": "何慧",
+ "name": "heh",
"login": "yystopf",
- "image_url": "images/avatars/User/6?t=1622513134"
+ "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png"
},
- "id": 7,
- "status": "common",
- "created_at": "2021-06-09 16:41",
- "time_ago": "1分钟前"
+ "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 GET \
--d "page=1" \
--d "limit=5" \
-http://localhost:3000/api/projects | jq
-await octokit.request('GET /api/projects')
+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 请求
-GET api/projects
+POST /api/:owner/:repo/project_invite_links/generate_link.json
请求参数
@@ -4589,63 +4599,29 @@ http://localhost:3000/api/projects | jq
-page
-false
-1
-string
-页数,第几页
-
-
-limit
-false
-15
-string
-每页多少条数据,默认15条
-
-
-sort_by
-false
-
-string
-排序类型, 取值:updated_on、created_on、forked_count、praises_count; updated_on: 更新时间排序,created_on: 创建时间排序,forked_count: fork数据排序,praises_count: 点赞数量排序,默认为updated_on更新时间排序
-
-
-sort_direction
-false
-
-string
-排序方式,取值为: desc、asc; desc: 降序排序, asc: 升序排序, 默认为:desc
-
-
-search
-false
+role
+是
string
-按照项目名称搜索
-
-
-category_id
-false
-
-int
-项目类别id
-
-
-language_id
-false
-
-int
-项目语言id
+项目权限,reporter: 报告者, developer: 开发者,manager:管理员
-project_type
-false
+is_apply
+是
-string
-项目类型, 取值为:common、mirror; common:开源托管项目, mirror:开源镜像项目
+boolean
+是否需要审核
-返回字段说明
+
+
+请求的JSON示例
+
+{
+ "role": "developer",
+ "is_apply": false
+}
+
返回字段说明
参数
@@ -4654,74 +4630,675 @@ http://localhost:3000/api/projects | jq
-total_count
+id
int
-项目总条数
+链接id
-id
+role
string
-项目id
+邀请角色
-name
+is_apply
+boolean
+是否需要审核
+
+
+sign
string
-项目名称
+邀请标识(放在链接后面即可)
-description
+expired_at
string
-项目简介
+链接过期时间
-visits
+user.id
int
-流量数
+链接创建者的id
-forked_count
-int
-被fork的数量
+user.type
+string
+链接创建者的类型
-praises_count
-int
-star数量
+user.name
+string
+链接创建者的名称
-is_public
-boolean
-是否公开, true:公开,false:未公开
+user.login
+string
+链接创建者的标识
-mirror_url
+user.image_url
string
-镜像url
+链接创建者头像
-last_update_time
+project.id
int
-最后更新时间,为UNIX格式的时间戳
+链接关联项目的id
-author
-object
-项目创建者
+project.identifier
+string
+链接关联项目的标识
--- name
+project.name
string
-用户名,也是用户标识
+链接关联项目的名称
-category
-object
-项目类别
+project.description
+string
+链接关联项目的描述
--- id
-int
-项目类型id
+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"
+}
+
申请加入项目
+申请加入项目
+
+
+示例:
+
+curl -X POST http://localhost:3000/api/applied_projects.json
+
await octokit.request('POST /api/appliedr_projects.json')
+
HTTP 请求
+POST /api/applied_projects.json
+请求参数
+
+
+参数
+必选
+默认
+类型
+字段说明
+
+
+
+applied_project.code
+是
+
+string
+邀请码
+
+
+applied_project.role
+否
+
+string
+项目权限,reporter: 报告者, developer: 开发者,manager:管理员
+
+
+
+
+请求的JSON示例
+
+{
+ "applied_project": {
+ "code": "1una34",
+ "role": "developer"
+ }
+}
+
返回字段说明
+
+
+参数
+类型
+字段说明
+
+
+
+id
+int
+申请id
+
+
+status
+string
+申请状态,canceled:取消,common:正在申请, accept:已接受,refuse:已拒绝
+
+
+time_ago
+string
+项目申请创建的时间
+
+
+project.id
+int
+申请项目的id
+
+
+project.identifier
+string
+申请项目的标识
+
+
+project.name
+string
+申请项目的名称
+
+
+project.description
+string
+申请项目的描述
+
+
+project.is_public
+bool
+申请项目是否公开
+
+
+project.owner.id
+bool
+申请项目拥有者id
+
+
+project.owner.type
+string
+申请项目拥有者类型
+
+
+project.owner.name
+string
+申请项目拥有者昵称
+
+
+project.owner.login
+string
+申请项目拥有者标识
+
+
+project.owner.image_url
+string
+申请项目拥有者头像
+
+
+user.id
+int
+申请创建者的id
+
+
+user.type
+string
+申请创建者的类型
+
+
+user.name
+string
+申请创建者的名称
+
+
+user.login
+string
+申请创建者的标识
+
+
+user.image_url
+string
+申请创建者头像
+
+
+
+
+返回的JSON示例:
+
+{
+ "project": {
+ "id": 74,
+ "identifier": "hehuisssjssjjsjs",
+ "name": "hehuisssjssjjsjs",
+ "description": "wwww",
+ "is_public": false,
+ "owner": {
+ "id": 10,
+ "type": "User",
+ "name": "testforge1",
+ "login": "testforge1",
+ "image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png"
+ }
+ },
+ "user": {
+ "id": 6,
+ "type": "User",
+ "name": "何慧",
+ "login": "yystopf",
+ "image_url": "images/avatars/User/6?t=1622513134"
+ },
+ "id": 7,
+ "status": "common",
+ "created_at": "2021-06-09 16:41",
+ "time_ago": "1分钟前"
+}
+
获取项目列表
+获取项目列表,也可以更加相关条件过滤搜素
+
+
+示例:
+
+curl -X GET \
+-d "page=1" \
+-d "limit=5" \
+http://localhost:3000/api/projects | jq
+
await octokit.request('GET /api/projects')
+
HTTP 请求
+GET api/projects
+请求参数
+
+
+参数
+必选
+默认
+类型
+字段说明
+
+
+
+page
+false
+1
+string
+页数,第几页
+
+
+limit
+false
+15
+string
+每页多少条数据,默认15条
+
+
+sort_by
+false
+
+string
+排序类型, 取值:updated_on、created_on、forked_count、praises_count; updated_on: 更新时间排序,created_on: 创建时间排序,forked_count: fork数据排序,praises_count: 点赞数量排序,默认为updated_on更新时间排序
+
+
+sort_direction
+false
+
+string
+排序方式,取值为: desc、asc; desc: 降序排序, asc: 升序排序, 默认为:desc
+
+
+search
+false
+
+string
+按照项目名称搜索
+
+
+category_id
+false
+
+int
+项目类别id
+
+
+language_id
+false
+
+int
+项目语言id
+
+
+project_type
+false
+
+string
+项目类型, 取值为:common、mirror; common:开源托管项目, mirror:开源镜像项目
+
+
+返回字段说明
+
+
+参数
+类型
+字段说明
+
+
+
+total_count
+int
+项目总条数
+
+
+id
+string
+项目id
+
+
+name
+string
+项目名称
+
+
+description
+string
+项目简介
+
+
+visits
+int
+流量数
+
+
+forked_count
+int
+被fork的数量
+
+
+praises_count
+int
+star数量
+
+
+is_public
+boolean
+是否公开, true:公开,false:未公开
+
+
+mirror_url
+string
+镜像url
+
+
+last_update_time
+int
+最后更新时间,为UNIX格式的时间戳
+
+
+author
+object
+项目创建者
+
+
+-- name
+string
+用户名,也是用户标识
+
+
+category
+object
+项目类别
+
+
+-- id
+int
+项目类型id
-- name
@@ -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