From c42f3d59281ba65edc85873e6ff3a9d551968410 Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 15 Jun 2022 18:07:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E9=82=80=E8=AF=B7?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=93=BE=E6=8E=A5=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project_invite_links_controller.rb | 36 +++++++++ .../project_invite_links/create_form.rb | 8 ++ app/models/applied_project.rb | 21 ++--- app/models/project.rb | 1 + app/models/project_invite_link.rb | 57 ++++++++++++++ app/services/projects/link_join_service.rb | 76 +++++++++++++++++++ app/views/projects/_detail.json.jbuilder | 8 ++ .../_detail.json.jbuilder | 12 +++ .../current_link.json.jbuilder | 1 + config/routes.rb | 7 ++ ...20614070028_create_project_invite_links.rb | 16 ++++ ...project_invite_link_to_applied_projects.rb | 6 ++ spec/models/project_invite_link_spec.rb | 5 ++ 13 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 app/controllers/projects/project_invite_links_controller.rb create mode 100644 app/forms/projects/project_invite_links/create_form.rb create mode 100644 app/models/project_invite_link.rb create mode 100644 app/services/projects/link_join_service.rb create mode 100644 app/views/projects/_detail.json.jbuilder create mode 100644 app/views/projects/project_invite_links/_detail.json.jbuilder create mode 100644 app/views/projects/project_invite_links/current_link.json.jbuilder create mode 100644 db/migrate/20220614070028_create_project_invite_links.rb create mode 100644 db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb create mode 100644 spec/models/project_invite_link_spec.rb 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..59f618fc0 --- /dev/null +++ b/app/controllers/projects/project_invite_links_controller.rb @@ -0,0 +1,36 @@ +class Projects::ProjectInviteLinksController < Projects::BaseController + before_action :require_manager!, except: [:redirect_link] + before_action :require_login + + def current_link + @project_invite_link = ProjectInviteLink.find_by(user_id: current_user.id, project_id: @project.id) + @project_invite_link = ProjectInviteLink.build!(@project, current_user) 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}) + puts params_data + Projects::ProjectInviteLinks::CreateForm.new(params_data).validate! + @project_invite_link = ProjectInviteLink.create!(params_data.merge(sign: ProjectInviteLink.random_hex_sign)) + render_ok + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + 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/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 edaa00eb5..92e1bac77 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..fa5c0c440 --- /dev/null +++ b/app/models/project_invite_link.rb @@ -0,0 +1,57 @@ +# == 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 + SecureRandom.hex + 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 + ) + end + + private + def set_old_data_expired_at + ProjectInviteLink.where(user_id: self.user_id, project_id: self.project).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..c42376ef2 --- /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) + 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..7c9f78ddf --- /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.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/config/routes.rb b/config/routes.rb index b585f0775..ed28b5ba2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -603,6 +603,13 @@ Rails.application.routes.draw do post :cancel end end + resources :project_invite_links, only: [:index] do + collection do + get :current_link + post :generate_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/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