合并组织模块功能

This commit is contained in:
jasder 2021-02-06 16:21:47 +08:00
commit 8e4d6c7064
98 changed files with 2089 additions and 113 deletions

1
.gitignore vendored
View File

@ -36,6 +36,7 @@ public/react/yarn.lock
/.idea/*
# Ignore react node_modules
public/react/*
/public/react/.cache
/public/react/node_modules/
/public/react/config/stats.json

View File

@ -113,6 +113,8 @@ http://localhost:3000/
### API
- [API](api_document.md)
- [API](showdoc.com.cn)
账号forgeplus@admin.com 密码forge123
## 贡献代码

View File

@ -680,6 +680,14 @@ class ApplicationController < ActionController::Base
relation.page(page).per(limit)
end
def kaminari_array_paginate(relation)
limit = params[:limit] || params[:per_page]
limit = (limit.to_i.zero? || limit.to_i > 15) ? 15 : limit.to_i
page = params[:page].to_i.zero? ? 1 : params[:page].to_i
Kaminari.paginate_array(relation).page(page).per(limit)
end
def strf_time(time)
time.blank? ? '' : time.strftime("%Y-%m-%d %H:%M:%S")
end

View File

@ -1,12 +1,14 @@
module PaginateHelper
def paginate(objs, **opts)
page = params[:page].to_i <= 0 ? 1 : params[:page].to_i
per_page = params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : opts[:per_page] || 20
if objs.is_a?(Array)
Kaminari.paginate_array(objs).page(page).per(per_page)
def paginate(relation)
limit = params[:limit] || params[:per_page]
limit = (limit.to_i.zero? || limit.to_i > 15) ? 15 : limit.to_i
page = params[:page].to_i.zero? ? 1 : params[:page].to_i
if relation.is_a?(Array)
Kaminari.paginate_array(relation).page(page).per(limit)
else
objs.page(page).per(per_page)
relation.page(page).per(limit)
end
end
end

View File

@ -0,0 +1,34 @@
class Organizations::BaseController < ApplicationController
include ApplicationHelper
include PaginateHelper
protected
def can_edit_org?
current_user.admin? || @organization.is_owner?(current_user.id)
end
def check_user_can_edit_org
tip_exception("您没有权限进行该操作") unless can_edit_org?
end
def org_limited_condition
@organization.organization_extension.limited? && !current_user.logged?
end
def org_privacy_condition
@organization.organization_extension.privacy? && @organization.organization_users.where(user_id: current_user.id).blank?
end
def team_not_found_condition
@team.team_users.where(user_id: current_user.id).blank? && !@organization.is_owner?(current_user.id)
end
def user_mark
params[:username] || params[:id]
end
def project_mark
params[:repo_name] || params[:id]
end
end

View File

@ -0,0 +1,57 @@
class Organizations::OrganizationUsersController < Organizations::BaseController
before_action :load_organization
before_action :load_operate_user, :load_organization_user, :check_user_can_edit_org, only: [:destroy]
def index
@organization_users = @organization.organization_users.includes(:user)
search = params[:search].to_s.downcase
@organization_users = @organization_users.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.login, users.mail)) LIKE ?", "%#{search.split(" ").join('|')}%") if search.present?
@organization_users = kaminari_paginate(@organization_users)
end
def destroy
tip_exception("您不能从所有者团队中删除最后一个用户") if @organization.is_owner_team_last_one?(@operate_user.id)
ActiveRecord::Base.transaction do
@organization_user.destroy!
TeamUser.where(organization_id: @organization.id, user_id: @operate_user.id).map{|u| u.destroy!}
Gitea::Organization::OrganizationUser::DeleteService.call(@organization.gitea_token, @organization.login, @operate_user.login)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def quit
@organization_user = @organization.organization_users.find_by(user_id: current_user.id)
tip_exception("您不在该组织中") if @organization_user.nil?
tip_exception("您不能从所有者团队中删除最后一个用户") if @organization.is_owner_team_last_one?(current_user.id)
ActiveRecord::Base.transaction do
@organization_user.destroy!
TeamUser.where(organization_id: @organization.id, user_id: current_user.id).map{|u| u.destroy!}
Gitea::Organization::OrganizationUser::DeleteService.call(@organization.gitea_token, @organization.login, current_user.login)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
private
def load_organization
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def load_operate_user
@operate_user = User.find_by(login: user_mark) if user_mark.present?
tip_exception("平台用户不存在") if @operate_user.nil?
end
def load_organization_user
@organization_user = OrganizationUser.find_by(organization_id: @organization.id, user_id: @operate_user.id)
tip_exception("组织成员不存在") if @organization_user.nil?
end
end

View File

@ -0,0 +1,106 @@
class Organizations::OrganizationsController < Organizations::BaseController
before_action :require_login, except: [:index, :show]
before_action :convert_image!, only: [:create, :update]
before_action :load_organization, only: [:show, :update, :destroy]
before_action :check_user_can_edit_org, only: [:update, :destroy]
def index
if current_user.logged?
logged_organizations_sql = Organization.with_visibility(%w(common limited)).to_sql
privacy_organizations_sql = Organization.with_visibility("privacy").joins(:organization_users).where(organization_users: {user_id: current_user.id}).to_sql
@organizations = Organization.from("( #{ logged_organizations_sql } UNION #{ privacy_organizations_sql } ) AS users")
else
@organizations = Organization.with_visibility("common")
end
@organizations = @organizations.ransack(login_cont: params[:search]).result if params[:search].present?
@organizations = @organizations.includes(:organization_extension).order("organization_extensions.#{sort_by} #{sort_direction}")
@organizations = kaminari_paginate(@organizations)
end
def show
@can_create_project = @organization.can_create_project?(current_user.id)
@is_admin = can_edit_org?
@is_member = @organization.is_member?(current_user.id)
end
def create
ActiveRecord::Base.transaction do
@organization = Organizations::CreateService.call(current_user, organization_params)
Util.write_file(@image, avatar_path(@organization)) if params[:image].present?
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def update
ActiveRecord::Base.transaction do
login = @organization.login
@organization.update!(login: organization_params[:name]) if organization_params[:name].present?
@organization.organization_extension.update_attributes!(organization_params.except(:name))
Gitea::Organization::UpdateService.call(@organization.gitea_token, login, @organization.reload)
Util.write_file(@image, avatar_path(@organization)) if params[:image].present?
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def destroy
tip_exception("密码不正确") unless current_user.check_password?(password)
ActiveRecord::Base.transaction do
Gitea::Organization::DeleteService.call(@organization.gitea_token, @organization.login)
@organization.destroy!
end
render_ok
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
private
def convert_image!
return unless params[:image].present?
max_size = EduSetting.get('upload_avatar_max_size') || 2 * 1024 * 1024 # 2M
if params[:image].class == ActionDispatch::Http::UploadedFile
@image = params[:image]
render_error('请上传文件') if @image.size.zero?
render_error('文件大小超过限制') if @image.size > max_size.to_i
else
image = params[:image].to_s.strip
return render_error('请上传正确的图片') if image.blank?
@image = Util.convert_base64_image(image, max_size: max_size.to_i)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
def avatar_path(organization)
ApplicationController.helpers.disk_filename(organization.class, organization.id)
end
def organization_params
params.permit(:name, :description, :website, :location,
:repo_admin_change_team_access, :visibility,
:max_repo_creation)
end
def password
params.fetch(:password, "")
end
def load_organization
@organization = Organization.find_by(login: params[:id]) || Organization.find_by(id: params[:id])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def sort_by
params.fetch(:sort_by, "created_at")
end
def sort_direction
params.fetch(:sort_direction, "desc")
end
end

View File

@ -0,0 +1,45 @@
class Organizations::ProjectsController < Organizations::BaseController
before_action :load_organization
def index
public_projects_sql = @organization.projects.where(is_public: true).to_sql
private_projects_sql = @organization.projects
.where(is_public: false)
.joins(team_projects: {team: :team_users})
.where(team_users: {user_id: current_user.id}).to_sql
@projects = Project.from("( #{ public_projects_sql} UNION #{ private_projects_sql } ) AS projects")
@projects = @projects.ransack(name_or_identifier_cont: params[:search]).result if params[:search].present?
@projects = @projects.includes(:owner).order("projects.#{sort} #{sort_direction}")
@projects = paginate(@projects)
end
def search
tip_exception("请输入搜索关键词") if params[:search].nil?
public_projects_sql = @organization.projects.where(is_public: true).to_sql
private_projects_sql = @organization.projects
.where(is_public: false)
.joins(team_projects: {team: :team_users})
.where(team_users: {user_id: current_user.id}).to_sql
@projects = Project.from("( #{ public_projects_sql} UNION #{ private_projects_sql } ) AS projects")
@projects = @projects.ransack(name_or_identifier_cont: params[:search]).result
@projects = @projects.includes(:owner).order("projects.#{sort} #{sort_direction}")
end
private
def load_organization
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def sort
params.fetch(:sort_by, "updated_on")
end
def sort_direction
params.fetch(:sort_direction, "desc")
end
end

View File

@ -0,0 +1,58 @@
class Organizations::TeamProjectsController < Organizations::BaseController
before_action :load_organization
before_action :load_team
before_action :load_operate_project, :check_user_can_edit_org, only: [:create, :destroy]
before_action :load_team_project, only: [:destroy]
def index
@team_projects = @team.team_projects
@team_projects = paginate(@team_projects)
end
def create
tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project
ActiveRecord::Base.transaction do
@team_project = TeamProject.build(@organization.id, @team.id, @operate_project.id)
Gitea::Organization::TeamProject::CreateService.call(@organization.gitea_token, @team.gtid, @organization.login, @operate_project.identifier)
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def destroy
tip_exception("该组织团队项目包括组织所有项目,不允许更改") if @team.includes_all_project
ActiveRecord::Base.transaction do
@team_project.destroy!
Gitea::Organization::TeamProject::DeleteService.call(@organization.gitea_token, @team.gtid, @organization.login, @operate_project.identifier)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
private
def load_organization
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def load_team
@team = Team.find_by_id(params[:team_id])
return render_not_found("组织团队不存在") if @team.nil?
return render_forbidden("没有查看组织团队的权限") if team_not_found_condition
end
def load_operate_project
@operate_project = Project.find_by(id: project_mark) || Project.find_by(identifier: project_mark)
tip_exception("项目不存在") if @operate_project.nil?
end
def load_team_project
@team_project = TeamProject.find_by(organization_id: @organization.id, team_id: @team.id, project_id: @operate_project.id)
tip_exception("组织团队项目不存在") if @team_project.nil?
end
end

View File

@ -0,0 +1,76 @@
class Organizations::TeamUsersController < Organizations::BaseController
before_action :load_organization, :load_team
before_action :load_operate_user, only: [:create, :destroy]
before_action :load_team_user, only: [:destroy]
before_action :check_user_can_edit_org, only: [:create, :destroy]
def index
@team_users = @team.team_users.includes(:user)
search = params[:search].to_s.downcase
@team_users = @team_users.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.login, users.mail, users.nickname)) LIKE ?", "%#{search.split(" ").join('|')}%") if search.present?
@team_users = kaminari_paginate(@team_users)
end
def create
ActiveRecord::Base.transaction do
@team_user = TeamUser.build(@organization.id, @operate_user.id, @team.id)
@organization_user = OrganizationUser.build(@organization.id, @operate_user.id)
Gitea::Organization::TeamUser::CreateService.call(@organization.gitea_token, @team.gtid, @operate_user.login)
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def destroy
tip_exception("您不能从所有者团队中删除最后一个用户") if @team.owner? && @organization.is_owner_team_last_one?(@operate_user.id)
ActiveRecord::Base.transaction do
@team_user.destroy!
Gitea::Organization::TeamUser::DeleteService.call(@organization.gitea_token, @team.gtid, @operate_user.login)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def quit
@team_user = @team.team_users.find_by(user_id: current_user.id)
tip_exception("您不在该组织团队中") if @team_user.nil?
tip_exception("您不能从所有者团队中删除最后一个用户") if @team.owner? && @organization.is_owner_team_last_one?(current_user.id)
ActiveRecord::Base.transaction do
@team_user.destroy!
Gitea::Organization::TeamUser::DeleteService.call(@organization.gitea_token, @team.gtid, current_user.login)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
private
def load_organization
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def load_team
@team = Team.find_by_id(params[:team_id])
return render_not_found("组织团队不存在") if @team.nil?
return render_forbidden("没有查看组织团队的权限") if team_not_found_condition
end
def load_operate_user
@operate_user = User.find_by(login: user_mark) if user_mark.present?
tip_exception("平台用户不存在") if @operate_user.nil?
end
def load_team_user
@team_user = TeamUser.find_by(team_id: @team.id, user_id: @operate_user.id)
tip_exception("组织团队成员不存在") if @team_user.nil?
end
end

View File

@ -0,0 +1,77 @@
class Organizations::TeamsController < Organizations::BaseController
before_action :load_organization
before_action :load_team, only: [:show, :update, :destroy]
before_action :check_user_can_edit_org, only: [:create, :update, :destroy]
def index
#if @organization.is_owner?(current_user) || current_user.admin?
@teams = @organization.teams
#else
# @teams = @organization.teams.joins(:team_users).where(team_users: {user_id: current_user.id})
#end
@is_admin = can_edit_org?
@teams = @teams.includes(:team_units, :team_users)
@teams = kaminari_paginate(@teams)
end
def search
tip_exception("请输入搜索关键词") if params[:search].nil?
if @organization.is_owner?(current_user) || current_user.admin?
@teams = @organization.teams
else
@teams = @organization.teams.joins(:team_users).where(team_users: {user_id: current_user.id})
end
@is_admin = can_edit_org?
@teams = @teams.ransack(name_cont: params[:search]).result if params[:search].present?
@teams = @teams.includes(:team_units, :team_users)
end
def show
@is_admin = can_edit_org?
@is_member = @team.is_member?(current_user.id)
end
def create
@team = Organizations::Teams::CreateService.call(current_user, @organization, team_params)
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def update
@team = Organizations::Teams::UpdateService.call(current_user, @team, team_params)
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def destroy
tip_exception("组织团队不允许被删除") if @team.owner?
ActiveRecord::Base.transaction do
Gitea::Organization::Team::DeleteService.call(@organization.gitea_token, @team.gtid)
@team.destroy!
end
render_ok
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
private
def team_params
params.permit(:name, :description, :authorize, :includes_all_project, :can_create_org_project, :unit_types => [])
end
def load_organization
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
return render_not_found("组织不存在") if @organization.nil?
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
end
def load_team
@team = Team.find_by_id(params[:id])
return render_not_found("组织团队不存在") if @team.nil?
return render_forbidden("没有查看组织团队的权限") if team_not_found_condition
end
end

View File

@ -0,0 +1,12 @@
class OwnersController < ApplicationController
before_action :require_login
def index
@owners = []
@owners += [current_user]
@owners += Organization.joins(team_users: :team)
.where(team_users: {user_id: current_user.id},
teams: {can_create_org_project: true})
.distinct
end
end

View File

@ -0,0 +1,47 @@
class Projects::TeamsController < Projects::BaseController
before_action :load_operate_team, only: [:create, :destroy]
before_action :load_team_project, only: :destroy
def index
if @project.owner.is_a?(Organization)
@teams = Team.joins(:team_projects).where(team_projects: {project_id: @project.id})
else
@teams = Team.none
end
@teams = paginate(@teams)
end
def create
ActiveRecord::Base.transaction do
@team_project = TeamProject.build(@owner.id, @operate_team.id, @project.id)
Gitea::Organization::TeamProject::CreateService.call(@owner.gitea_token, @operate_team.gtid, @owner.login, @project.identifier)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def destroy
ActiveRecord::Base.transaction do
@team_project.destroy!
Gitea::Organization::TeamProject::DeleteService.call(@owner.gitea_token, @operate_team.gtid, @owner.login, @project.identifier)
render_ok
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
private
def load_operate_team
@operate_team = Team.find_by(id: params[:team_id]) || Team.find_by(id: params[:id])
tip_exception("项目不存在") if @operate_team.nil?
tip_exception("该组织团队拥有组织所有项目,无法进行操作") if @operate_team.includes_all_project
end
def load_team_project
@team_project = TeamProject.find_by(organization_id: @owner.id, team_id: @operate_team.id, project_id: @project.id)
tip_exception("组织团队项目不存在") if @team_project.nil?
end
end

View File

@ -11,7 +11,7 @@ class ProjectsController < ApplicationController
scope = Projects::ListQuery.call(params)
# @projects = kaminari_paginate(scope)
@projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, owner: :user_extension)
@projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner)
category_id = params[:category_id]
@total_count =
@ -128,7 +128,7 @@ class ProjectsController < ApplicationController
end
def recommend
@projects = Project.recommend.includes(:repository, :project_category, owner: :user_extension).limit(5)
@projects = Project.recommend.includes(:repository, :project_category, :owner).limit(5)
end
def about
@ -163,7 +163,7 @@ class ProjectsController < ApplicationController
private
def project_params
params.permit(:user_id, :name, :description, :repository_name,
:project_category_id, :project_language_id, :license_id, :ignore_id)
:project_category_id, :project_language_id, :license_id, :ignore_id, :private)
end
def mirror_params

View File

@ -0,0 +1,25 @@
class Users::OrganizationsController < Users::BaseController
def index
if current_user.logged?
logged_organizations_sql = observed_user.organizations.with_visibility(%w(common limited)).to_sql
privacy_organizations_sql = observed_user.organizations.with_visibility("privacy").joins(:organization_users).where(organization_users: {user_id: current_user.id}).to_sql
@organizations = Organization.from("( #{ logged_organizations_sql } UNION #{ privacy_organizations_sql } ) AS users")
else
@organizations = observed_user.organizations.with_visibility("common")
end
@organizations = @organizations.ransack(login_cont: params[:search]).result if params[:search].present?
@organizations = @organizations.includes(:organization_extension).order("organization_extensions.#{sort_by} #{sort_direction}")
@organizations = kaminari_paginate(@organizations)
end
private
def sort_by
params.fetch(:sort_by, "created_at")
end
def sort_direction
params.fetch(:sort_direction, "desc")
end
end

View File

@ -31,6 +31,7 @@ class UsersController < ApplicationController
#用户的组织数量
# @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
@projects_common_count = user_projects.common.size
@projects_mirrior_count = user_projects.mirror.size

View File

@ -124,7 +124,7 @@ class VersionReleasesController < ApplicationController
private
def set_user
@user = @repository.user
@user = @repository.owner
end
def find_version

View File

@ -1,13 +1,13 @@
class Projects::CreateForm < BaseForm
REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
attr_accessor :user_id, :name, :description, :repository_name, :project_category_id,
:project_language_id, :ignore_id, :license_id, :private
:project_language_id, :ignore_id, :license_id, :private, :owner
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: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
validate :check_ignore, :check_license
validate :check_ignore, :check_license, :check_owner, :check_max_repo_creation
validate do
check_project_category(project_category_id)
check_project_language(project_language_id)
@ -20,4 +20,15 @@ class Projects::CreateForm < BaseForm
def check_ignore
raise "ignore_id值无效." if ignore_id && Ignore.find_by(id: ignore_id).blank?
end
def check_owner
@owner = Owner.find_by(id: user_id)
raise "user_id值无效." if user_id && owner.blank?
end
def check_max_repo_creation
return unless owner.is_a?(Organization)
return if owner.max_repo_creation <= -1
raise "已超过组织设置最大仓库数" if owner.max_repo_creation == owner.num_projects
end
end

View File

@ -143,7 +143,7 @@ module ApplicationHelper
def url_to_avatar(source)
if File.exist?(disk_filename(source&.class, source&.id))
ctime = File.ctime(disk_filename(source.class, source.id)).to_i
if source.class.to_s == 'User'
if %w(User Organization).include?(source.class.to_s)
File.join(relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
else
File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"

View File

@ -61,12 +61,14 @@ module ProjectsHelper
{
login: project.project_educoder.owner,
name: project.project_educoder.owner,
type: 'Educoder',
image_url: project.project_educoder.image_url
}
else
{
login: @owner.login,
name: @owner.real_name,
type: @owner.type,
image_url: url_to_avatar(@owner)
}
end

View File

@ -5,7 +5,7 @@ class SyncMirroredRepositoryJob < ApplicationJob
repo = Repository.find_by(id: repo_id)
current_user = User.find_by(id: user_id)
return if repo.blank? || current_user.blank?
result = Gitea::Repository::SyncMirroredService.new(repo.user.login, repo.identifier, token: current_user.gitea_token).call
result = Gitea::Repository::SyncMirroredService.new(repo.owner.login, repo.identifier, token: current_user.gitea_token).call
repo&.mirror.set_status! if result[:status] === 200
end
end

View File

@ -8,11 +8,12 @@ module ProjectOperable
has_many :developers, -> { joins(:roles).where(roles: { name: 'Developer' }) }, class_name: 'Member'
has_many :reporters, -> { joins(:roles).where(roles: { name: 'Reporter' }) }, class_name: 'Member'
has_many :writable_members, -> { joins(:roles).where.not(roles: {name: 'Reporter'}) }, class_name: 'Member'
has_many :team_projects, dependent: :destroy
end
def add_member!(user_id, role_name='Developer')
member = members.create!(user_id: user_id)
set_developer_role(member)
set_developer_role(member, role_name)
end
def remove_member!(user_id)
@ -21,7 +22,13 @@ module ProjectOperable
end
def member?(user_id)
members.exists?(user_id: user_id)
if owner.is_a?(User)
members.exists?(user_id: user_id)
elsif owner.is_a?(Organization)
members.exists?(user_id: user_id) || team_projects.joins(team: :team_users).where(team_users: {user_id: user_id}).present?
else
false
end
end
# 除了项目创建者本身
@ -35,26 +42,50 @@ module ProjectOperable
end
def owner?(user)
self.owner == user
if owner.is_a?(User)
self.owner == user
elsif owner.is_a?(Organization)
owner.is_owner?(user.id)
else
false
end
end
# 项目管理员(包含项目拥有者),权限:仓库设置、仓库可读可写
def manager?(user)
managers.exists?(user_id: user.id)
if owner.is_a?(User)
managers.exists?(user_id: user.id)
elsif owner.is_a?(Organization)
managers.exists?(user_id: user.id) || owner.is_admin?(user.id)
else
false
end
end
# 项目开发者,可读可写权限
def develper?(user)
developers.exists?(user_id: user.id)
if owner.is_a?(User)
developers.exists?(user_id: user.id)
elsif owner.is_a?(Organization)
developers.exists?(user_id: user.id) || owner.is_write?(user.id)
else
false
end
end
# 报告者,只有可读权限
def reporter?(user)
reporters.exists?(user_id: user.id)
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)
else
false
end
end
def set_developer_role(member)
role = Role.find_by_name 'Developer'
def set_developer_role(member, role_name)
role = Role.find_by(name: role_name)
member.member_roles.create!(role: role)
end

View File

@ -11,6 +11,11 @@
# sync_subject :boolean default("0")
# sync_shixun :boolean default("0")
#
# Indexes
#
# index_laboratories_on_identifier (identifier) UNIQUE
# index_laboratories_on_school_id (school_id)
#
class Laboratory < ApplicationRecord
belongs_to :school, optional: true

View File

@ -6,6 +6,10 @@
# laboratory_id :integer
# config :text(65535)
#
# Indexes
#
# index_laboratory_settings_on_laboratory_id (laboratory_id)
#
class LaboratorySetting < ApplicationRecord
belongs_to :laboratory

127
app/models/organization.rb Normal file
View File

@ -0,0 +1,127 @@
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# login :string(255) default(""), not null
# hashed_password :string(40) default(""), not null
# firstname :string(30) default(""), not null
# lastname :string(255) default(""), not null
# mail :string(60)
# admin :boolean default("0"), not null
# status :integer default("1"), not null
# last_login_on :datetime
# language :string(5) default("")
# auth_source_id :integer
# created_on :datetime
# updated_on :datetime
# type :string(255)
# identity_url :string(255)
# mail_notification :string(255) default(""), not null
# salt :string(64)
# gid :integer
# visits :integer default("0")
# excellent_teacher :integer default("0")
# excellent_student :integer default("0")
# phone :string(255)
# authentication :boolean default("0")
# grade :integer default("0")
# experience :integer default("0")
# nickname :string(255)
# show_realname :boolean default("1")
# professional_certification :boolean default("0")
# ID_number :string(255)
# certification :integer default("0")
# homepage_teacher :boolean default("0")
# homepage_engineer :boolean default("0")
# is_test :integer default("0")
# ecoder_user_id :integer default("0")
# business :boolean default("0")
# profile_completed :boolean default("0")
# laboratory_id :integer
# platform :string(255) default("0")
# gitea_token :string(255)
# gitea_uid :integer
# is_shixun_marker :boolean default("0")
# is_sync_pwd :boolean default("1")
# watchers_count :integer default("0")
# devops_step :integer default("0")
#
# Indexes
#
# index_users_on_ecoder_user_id (ecoder_user_id)
# index_users_on_homepage_engineer (homepage_engineer)
# index_users_on_homepage_teacher (homepage_teacher)
# index_users_on_laboratory_id (laboratory_id)
# index_users_on_login (login)
# index_users_on_mail (mail)
# index_users_on_type (type)
#
class Organization < Owner
alias_attribute :name, :login
NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
default_scope { where(type: "Organization") }
has_one :organization_extension, dependent: :destroy
has_many :teams, dependent: :destroy
has_many :organization_users, dependent: :destroy
has_many :team_users, dependent: :destroy
validates :login, presence: true
validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, case_sensitive: false
validates :login, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
delegate :description, :website, :location, :repo_admin_change_team_access,
:visibility, :max_repo_creation, :num_projects, :num_users, :num_teams, to: :organization_extension, allow_nil: true
scope :with_visibility, ->(visibility) { joins(:organization_extension).where(organization_extensions: {visibility: visibility}) if visibility.present? }
def self.build(name, gitea_token=nil)
self.create!(login: name, gitea_token: gitea_token)
end
def can_create_project?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {can_create_org_project: true}).present?
end
def is_member?(user_id)
organization_users.where(user_id: user_id).present?
end
def is_owner?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(owner)}).present?
end
def is_admin?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(admin owner)}).present?
end
def is_write?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(write admin owner)}).present?
end
def is_read?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).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 real_name
login
end
def show_real_name
name = lastname + firstname
if name.blank?
nickname.blank? ? login : nickname
else
name
end
end
end

View File

@ -0,0 +1,42 @@
# == Schema Information
#
# Table name: organization_extensions
#
# id :integer not null, primary key
# organization_id :integer
# description :string(255)
# website :string(255)
# location :string(255)
# repo_admin_change_team_access :boolean default("0")
# visibility :integer default("0")
# max_repo_creation :integer default("-1")
# created_at :datetime not null
# updated_at :datetime not null
# num_projects :integer default("0")
# num_users :integer default("0")
# num_teams :integer default("0")
#
# Indexes
#
# index_organization_extensions_on_organization_id (organization_id)
#
class OrganizationExtension < ApplicationRecord
belongs_to :organization
has_many :organization_users, foreign_key: :organization_id, primary_key: :organization_id
has_many :projects, foreign_key: :user_id, primary_key: :organization_id
has_many :teams, foreign_key: :organization_id, primary_key: :organization_id
enum visibility: {common: 0, limited: 1, privacy: 2}
def self.build(organization_id, description, website, location, repo_admin_change_team_access, visibility, max_repo_creation)
self.create!(organization_id: organization_id,
description: description,
website: website,
location: location,
repo_admin_change_team_access: repo_admin_change_team_access,
visibility: visibility,
max_repo_creation: max_repo_creation)
end
end

View File

@ -0,0 +1,34 @@
# == Schema Information
#
# Table name: organization_users
#
# id :integer not null, primary key
# user_id :integer
# organization_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_organization_users_on_organization_id (organization_id)
# index_organization_users_on_user_id (user_id)
#
class OrganizationUser < ApplicationRecord
belongs_to :organization
belongs_to :organization_extension, foreign_key: :organization_id, primary_key: :organization_id, counter_cache: :num_users
belongs_to :user
validates :user_id, uniqueness: {scope: :organization_id}
def self.build(organization_id, user_id)
org_user = self.find_by(organization_id: organization_id, user_id: user_id)
return org_user unless org_user.nil?
self.create!(organization_id: organization_id, user_id: user_id)
end
def teams
organization.teams.joins(:team_users).where(team_users: {user_id: user_id})
end
end

68
app/models/owner.rb Normal file
View File

@ -0,0 +1,68 @@
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# login :string(255) default(""), not null
# hashed_password :string(40) default(""), not null
# firstname :string(30) default(""), not null
# lastname :string(255) default(""), not null
# mail :string(60)
# admin :boolean default("0"), not null
# status :integer default("1"), not null
# last_login_on :datetime
# language :string(5) default("")
# auth_source_id :integer
# created_on :datetime
# updated_on :datetime
# type :string(255)
# identity_url :string(255)
# mail_notification :string(255) default(""), not null
# salt :string(64)
# gid :integer
# visits :integer default("0")
# excellent_teacher :integer default("0")
# excellent_student :integer default("0")
# phone :string(255)
# authentication :boolean default("0")
# grade :integer default("0")
# experience :integer default("0")
# nickname :string(255)
# show_realname :boolean default("1")
# professional_certification :boolean default("0")
# ID_number :string(255)
# certification :integer default("0")
# homepage_teacher :boolean default("0")
# homepage_engineer :boolean default("0")
# is_test :integer default("0")
# ecoder_user_id :integer default("0")
# business :boolean default("0")
# profile_completed :boolean default("0")
# laboratory_id :integer
# platform :string(255) default("0")
# gitea_token :string(255)
# gitea_uid :integer
# is_shixun_marker :boolean default("0")
# is_sync_pwd :boolean default("1")
# watchers_count :integer default("0")
# devops_step :integer default("0")
#
# Indexes
#
# index_users_on_ecoder_user_id (ecoder_user_id)
# index_users_on_homepage_engineer (homepage_engineer)
# index_users_on_homepage_teacher (homepage_teacher)
# index_users_on_laboratory_id (laboratory_id)
# index_users_on_login (login)
# index_users_on_mail (mail)
# index_users_on_type (type)
#
class Owner < ApplicationRecord
self.table_name = "users"
include ProjectAbility
has_many :projects, foreign_key: :user_id, dependent: :destroy
has_many :repositories, foreign_key: :user_id, dependent: :destroy
end

View File

@ -1,73 +1,74 @@
# == Schema Information
#
# Table name: projects
#
# id :integer not null, primary key
# name :string(255) default(""), not null
# description :text(4294967295)
# homepage :string(255) default("")
# is_public :boolean default("1"), not null
# parent_id :integer
# created_on :datetime
# updated_on :datetime
# identifier :string(255)
# status :integer default("1"), not null
# lft :integer
# rgt :integer
# inherit_members :boolean default("0"), not null
# project_type :integer default("0")
# hidden_repo :boolean default("0"), not null
# attachmenttype :integer default("1")
# user_id :integer
# dts_test :integer default("0")
# enterprise_name :string(255)
# organization_id :integer
# project_new_type :integer
# gpid :integer
# forked_from_project_id :integer
# forked_count :integer default("0")
# publish_resource :integer default("0")
# visits :integer default("0")
# hot :integer default("0")
# invite_code :string(255)
# qrcode :string(255)
# qrcode_expiretime :integer default("0")
# script :text(65535)
# training_status :integer default("0")
# 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")
# pull_requests_count :integer default("0")
# language :string(255)
# versions_count :integer default("0")
# issue_tags_count :integer default("0")
# closed_issues_count :integer default("0")
# open_devops :boolean default("0")
# gitea_webhook_id :integer
# open_devops_count :integer default("0")
# recommend :boolean default("0")
# platform :integer default("0")
#
# Indexes
#
# index_projects_on_forked_from_project_id (forked_from_project_id)
# index_projects_on_identifier (identifier)
# index_projects_on_is_public (is_public)
# index_projects_on_lft (lft)
# index_projects_on_name (name)
# index_projects_on_platform (platform)
# index_projects_on_project_type (project_type)
# index_projects_on_recommend (recommend)
# index_projects_on_rgt (rgt)
# index_projects_on_status (status)
# index_projects_on_updated_on (updated_on)
#
# == Schema Information
#
# Table name: projects
#
# id :integer not null, primary key
# name :string(255) default(""), not null
# description :text(4294967295)
# homepage :string(255) default("")
# is_public :boolean default("1"), not null
# parent_id :integer
# created_on :datetime
# updated_on :datetime
# identifier :string(255)
# status :integer default("1"), not null
# lft :integer
# rgt :integer
# inherit_members :boolean default("0"), not null
# project_type :integer default("0")
# hidden_repo :boolean default("0"), not null
# attachmenttype :integer default("1")
# user_id :integer
# dts_test :integer default("0")
# enterprise_name :string(255)
# organization_id :integer
# project_new_type :integer
# gpid :integer
# forked_from_project_id :integer
# forked_count :integer default("0")
# publish_resource :integer default("0")
# visits :integer default("0")
# hot :integer default("0")
# invite_code :string(255)
# qrcode :string(255)
# qrcode_expiretime :integer default("0")
# script :text(65535)
# training_status :integer default("0")
# 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")
# pull_requests_count :integer default("0")
# language :string(255)
# versions_count :integer default("0")
# issue_tags_count :integer default("0")
# closed_issues_count :integer default("0")
# open_devops :boolean default("0")
# gitea_webhook_id :integer
# open_devops_count :integer default("0")
# recommend :boolean default("0")
# platform :integer default("0")
#
# Indexes
#
# index_projects_on_forked_from_project_id (forked_from_project_id)
# index_projects_on_identifier (identifier)
# index_projects_on_is_public (is_public)
# index_projects_on_lft (lft)
# index_projects_on_name (name)
# index_projects_on_platform (platform)
# index_projects_on_project_type (project_type)
# index_projects_on_recommend (recommend)
# index_projects_on_rgt (rgt)
# index_projects_on_status (status)
# index_projects_on_updated_on (updated_on)
#
class Project < ApplicationRecord
include Matchable
include Publicable
@ -85,7 +86,8 @@ class Project < ApplicationRecord
belongs_to :ignore, optional: true
belongs_to :license, optional: true
belongs_to :owner, class_name: 'User', foreign_key: :user_id, optional: true
belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, optional: true
belongs_to :organization_extension, foreign_key: :user_id, primary_key: :organization_id, optional: true, counter_cache: :num_projects
belongs_to :project_category, optional: true , :counter_cache => true
belongs_to :project_language, optional: true , :counter_cache => true
has_many :project_trends, dependent: :destroy
@ -106,6 +108,7 @@ class Project < ApplicationRecord
has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
has_one :project_detail, dependent: :destroy
has_many :team_projects, 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)}
@ -248,7 +251,7 @@ class Project < ApplicationRecord
def self.find_with_namespace(namespace_path, identifier)
logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} "
user = User.find_by_login namespace_path
user = Owner.find_by_login namespace_path
project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
return nil if project.blank?

View File

@ -33,7 +33,7 @@
class Repository < ApplicationRecord
self.inheritance_column = nil # FIX The single-table inheritance mechanism failed
belongs_to :project, :touch => true
belongs_to :user, optional: true
belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, optional: true
has_one :mirror, foreign_key: :repo_id
has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', foreign_key: :repo_id
has_many :version_releases, dependent: :destroy

55
app/models/team.rb Normal file
View File

@ -0,0 +1,55 @@
# == Schema Information
#
# Table name: teams
#
# id :integer not null, primary key
# organization_id :integer
# name :string(255)
# description :string(255)
# authorize :integer default("0")
# num_projects :integer default("0")
# num_users :integer default("0")
# includes_all_project :boolean default("0")
# can_create_org_project :boolean default("0")
# gtid :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_teams_on_organization_id (organization_id)
#
class Team < ApplicationRecord
belongs_to :organization
belongs_to :organization_extension, foreign_key: :organization_id, primary_key: :organization_id, counter_cache: :num_teams
has_many :team_projects, dependent: :destroy
has_many :team_units, dependent: :destroy
has_many :team_users, dependent: :destroy
validates :name, uniqueness: {scope: :organization_id}
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)
self.create!(organization_id: organization_id,
name: name,
description: description,
authorize: authorize,
includes_all_project: includes_all_project,
can_create_org_project: can_create_org_project)
end
def setup_team_project!
return unless includes_all_project
organization.projects.each do |project|
TeamProject.build(organization.id, id, project.id)
end
end
def is_member?(user_id)
team_users.where(user_id: user_id).present?
end
end

View File

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: team_projects
#
# id :integer not null, primary key
# organization_id :integer
# project_id :integer
# team_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_team_projects_on_organization_id (organization_id)
# index_team_projects_on_project_id (project_id)
# index_team_projects_on_team_id (team_id)
#
class TeamProject < ApplicationRecord
belongs_to :organization
belongs_to :project
belongs_to :team, counter_cache: :num_projects
validates :project_id, uniqueness: {scope: [:organization_id, :team_id]}
def self.build(organization_id, team_id, project_id)
self.find_or_create_by!(organization_id: organization_id, team_id: team_id, project_id: project_id)
end
end

31
app/models/team_unit.rb Normal file
View File

@ -0,0 +1,31 @@
# == Schema Information
#
# Table name: team_units
#
# id :integer not null, primary key
# organization_id :integer
# team_id :integer
# unit_type :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_team_units_on_organization_id (organization_id)
# index_team_units_on_team_id (team_id)
#
class TeamUnit < ApplicationRecord
belongs_to :organization
belongs_to :team
enum unit_type: {code: 1, issues: 2, pulls: 3, releases: 4}
validates :unit_type, uniqueness: { scope: [:organization_id, :team_id]}
def self.build(organization_id, team_id, unit_type)
self.create!(organization_id: organization_id, team_id: team_id, unit_type: unit_type)
end
end

30
app/models/team_user.rb Normal file
View File

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: team_users
#
# id :integer not null, primary key
# organization_id :integer
# team_id :integer
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_team_users_on_organization_id (organization_id)
# index_team_users_on_team_id (team_id)
# index_team_users_on_user_id (user_id)
#
class TeamUser < ApplicationRecord
belongs_to :organization
belongs_to :team, counter_cache: :num_users
belongs_to :user
validates :user_id, uniqueness: {scope: [:organization_id, :team_id]}
def self.build(organization_id, user_id, team_id)
self.create!(organization_id: organization_id, user_id: user_id, team_id: team_id)
end
end

View File

@ -58,14 +58,13 @@
# index_users_on_type (type)
#
class User < ApplicationRecord
class User < Owner
default_scope {where(type: %w(User AnonymousUser))}
extend Enumerize
include Watchable
include Likeable
include BaseModel
include ProjectOperable
include ProjectAbility
include Droneable
# include Searchable::Dependents::User
@ -136,8 +135,8 @@ class User < ApplicationRecord
has_many :attachments,foreign_key: :author_id, :dependent => :destroy
# 关注
has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注
has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户
# has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注
# has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户
has_many :watchers, as: :watchable, dependent: :destroy
has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', dependent: :destroy
@ -154,21 +153,21 @@ class User < ApplicationRecord
# 项目
has_many :applied_projects, dependent: :destroy
has_many :projects, dependent: :destroy
has_many :repositories, dependent: :destroy
# 教学案例
# has_many :libraries, dependent: :destroy
has_many :project_trends, dependent: :destroy
has_many :oauths , dependent: :destroy
has_many :organization_users, dependent: :destroy
has_many :organizations, through: :organization_users
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
scope :like, lambda { |keywords|
where("LOWER(concat(lastname, firstname, login, mail)) LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank?
}
scope :simple_select, -> {select(:id, :login, :lastname,:firstname, :nickname, :gitea_uid)}
scope :simple_select, -> {select(:id, :login, :lastname,:firstname, :nickname, :gitea_uid, :type)}
attr_accessor :password, :password_confirmation

View File

@ -132,7 +132,8 @@ class Gitea::ClientService < ApplicationService
when 204
puts "[gitea] "
raise Error, "[gitea] delete ok"
# raise Error, "[gitea] delete ok"
{status: 204}
when 409
message = "创建失败,请检查该分支合并是否已存在"
raise Error, mark + message

View File

@ -0,0 +1,44 @@
class Gitea::Organization::CreateService < Gitea::ClientService
attr_reader :token, :org
def initialize(token, org)
@token = token
@org = org
end
def call
response = post(url, request_params)
render_status(response)
end
private
def request_params
create_params = {
username: org.login,
description: org.description,
location: org.location,
repo_admin_change_team_access: org.repo_admin_change_team_access,
visibility: visibility(org.visibility),
website: org.website,
max_repo_creation: org.max_repo_creation
}
Hash.new.merge(token: token, data: create_params)
end
def visibility(visibility)
case visibility
when "common"
"public"
when "limited"
"limited"
when "privacy"
"private"
else
"public"
end
end
def url
"/orgs".freeze
end
end

View File

@ -0,0 +1,23 @@
class Gitea::Organization::DeleteService < Gitea::ClientService
attr_reader :token, :name
def initialize(token, name)
@token = token
@name = name
end
def call
response = delete(url, params)
render_status(response)
end
private
def params
Hash.new.merge(token: token)
end
def url
"/orgs/#{name}".freeze
end
end

View File

@ -0,0 +1,23 @@
class Gitea::Organization::OrganizationUser::DeleteService < Gitea::ClientService
attr_reader :token, :org_name, :username
def initialize(token, org_name, username)
@token = token
@org_name = org_name
@username = username
end
def call
response = delete(url, params)
render_status(response)
end
private
def params
Hash.new.merge(token: token)
end
def url
"/orgs/#{org_name}/members/#{username}"
end
end

View File

@ -0,0 +1,25 @@
class Gitea::Organization::Repository::CreateService < Gitea::ClientService
attr_reader :token, :org_name, :params
def initialize(token, org_name, params)
@token = token
@org_name = org_name
@params = params
end
def call
response = post(url, request_params)
render_201_response(response)
end
private
def request_params
create_params = params.merge(readme: "readme")
Hash.new.merge(token: token, data: create_params)
end
def url
"/orgs/#{org_name}/repos".freeze
end
end

View File

@ -0,0 +1,50 @@
class Gitea::Organization::Team::CreateService < Gitea::ClientService
attr_reader :token, :org, :team
def initialize(token, org, team)
@token = token
@org = org
@team = team
end
def call
response = post(url, request_params)
render_status(response)
end
private
def request_params
create_params = {
name: team.name,
description: team.description,
permission: permission(team.authorize),
includes_all_repositories: team.includes_all_project,
can_create_org_repo: team.can_create_org_project,
units: unit_arrays
}
Hash.new.merge(token: token, data: create_params)
end
def permission(authorize)
case authorize
when "read"
"read"
when "write"
"write"
when "admin"
"admin"
when "owner"
"owner"
else
"none"
end
end
def unit_arrays
team.team_units.pluck(:unit_type).collect{|t|"repo.#{t}"}
end
def url
"/orgs/#{org.login}/teams".freeze
end
end

View File

@ -0,0 +1,22 @@
class Gitea::Organization::Team::DeleteService < Gitea::ClientService
attr_reader :token, :gtid
def initialize(token, gtid)
@token = token
@gtid = gtid
end
def call
response = delete(url, params)
render_status(response)
end
private
def params
Hash.new.merge(token: token)
end
def url
"/teams/#{gtid}".freeze
end
end

View File

@ -0,0 +1,49 @@
class Gitea::Organization::Team::UpdateService < Gitea::ClientService
attr_reader :token, :team
def initialize(token, team)
@token = token
@team = team
end
def call
response = patch(url, request_params)
render_status(response)
end
private
def request_params
update_params = {
name: team.name,
description: team.description,
permission: permission(team.authorize),
includes_all_repositories: team.includes_all_project,
can_create_org_repo: team.can_create_org_project,
units: unit_arrays
}
Hash.new.merge(token: token, data: update_params)
end
def permission(authorize)
case authorize
when "read"
"read"
when "write"
"write"
when "admin"
"admin"
when "owner"
"owner"
else
"none"
end
end
def unit_arrays
team.team_units.pluck(:unit_type).collect{|t|"repo.#{t}"}
end
def url
"/teams/#{team.gtid}".freeze
end
end

View File

@ -0,0 +1,24 @@
class Gitea::Organization::TeamProject::CreateService < Gitea::ClientService
attr_reader :token, :gtid, :org_name, :repo_name
def initialize(token, gtid, org_name, repo_name)
@token = token
@gtid = gtid
@org_name = org_name
@repo_name = repo_name
end
def call
response = put(url, request_params)
render_status(response)
end
private
def request_params
Hash.new.merge(token: token)
end
def url
"/teams/#{gtid}/repos/#{org_name}/#{repo_name}".freeze
end
end

View File

@ -0,0 +1,24 @@
class Gitea::Organization::TeamProject::DeleteService < Gitea::ClientService
attr_reader :token, :gtid, :org_name, :repo_name
def initialize(token, gtid, org_name, repo_name)
@token = token
@gtid = gtid
@org_name = org_name
@repo_name = repo_name
end
def call
response = delete(url, params)
render_status(response)
end
private
def params
Hash.new.merge(token: token)
end
def url
"/teams/#{gtid}/repos/#{org_name}/#{repo_name}".freeze
end
end

View File

@ -0,0 +1,23 @@
class Gitea::Organization::TeamUser::CreateService < Gitea::ClientService
attr_reader :token, :gtid, :username
def initialize(token, gtid, username)
@token = token
@gtid = gtid
@username = username
end
def call
response = put(url, request_params)
render_status(response)
end
private
def request_params
Hash.new.merge(token: token)
end
def url
"/teams/#{gtid}/members/#{username}".freeze
end
end

View File

@ -0,0 +1,23 @@
class Gitea::Organization::TeamUser::DeleteService < Gitea::ClientService
attr_reader :token, :gtid, :username
def initialize(token, gtid, username)
@token = token
@gtid = gtid
@username = username
end
def call
response = delete(url, params)
render_status(response)
end
private
def params
Hash.new.merge(token: token)
end
def url
"/teams/#{gtid}/members/#{username}"
end
end

View File

@ -0,0 +1,45 @@
class Gitea::Organization::UpdateService < Gitea::ClientService
attr_reader :token, :login, :org
def initialize(token, login, org)
@token = token
@login = login
@org = org
end
def call
response = patch(url, request_params)
render_status(response)
end
private
def request_params
update_params = {
name: org.login,
description: org.description,
location: org.location,
repo_admin_change_team_access: org.repo_admin_change_team_access,
visibility: visibility(org.visibility),
website: org.website,
max_repo_creation: org.max_repo_creation
}
Hash.new.merge(token: token, data: update_params)
end
def visibility(visibility)
case visibility
when "common"
"public"
when "limited"
"limited"
when "privacy"
"private"
else
"public"
end
end
def url
"/orgs/#{login}".freeze
end
end

View File

@ -0,0 +1,27 @@
class Gitea::Repository::TransferService < Gitea::ClientService
attr_reader :token, :owner, :repo, :new_owner
def initialize(token, owner, repo, new_owner)
@token = token
@owner = owner
@repo = repo
@new_owner = new_owner
end
def call
response = post(url, request_params)
render_status(response)
end
private
def request_params
transfer_params = {
new_owner: new_owner
}
Hash.new.merge(token: token, data: transfer_params)
end
def url
"/repos/#{owner}/#{repo}/transfer".freeze
end
end

View File

@ -0,0 +1,73 @@
class Organizations::CreateService < ApplicationService
attr_reader :user, :params
attr_accessor :organization, :gitea_organization, :owner_team
def initialize(user, params)
@user = user
@params = params
end
def call
Rails.logger.info("######Organization create_service begin######")
Rails.logger.info("######params #{params}######")
ActiveRecord::Base.transaction do
create_org_and_extension
create_owner_info
create_gitea_org
sync_gitea_info
Rails.logger.info("######Organization create_service end######")
end
@organization
end
private
def description
params[:description]
end
def website
params[:website]
end
def location
params[:location].present? ? params[:location] : nil
end
def repo_admin_change_team_access
params[:repo_admin_change_team_access].present? ? params[:repo_admin_change_team_access] : false
end
def visibility
params[:visibility].present? ? params[:visibility] : "common"
end
def max_repo_creation
params[:max_repo_creation].present? ? params[:max_repo_creation] : -1
end
def create_org_and_extension
@organization = Organization.build(params[:name], user.gitea_token)
org_extension = OrganizationExtension.build(organization.id, description, website,
location, repo_admin_change_team_access,
visibility, max_repo_creation)
end
def create_owner_info
@owner_team = Team.build(organization.id, "Owners", "", 4, true, true)
TeamUnit.unit_types.keys.each do |u_type|
TeamUnit.build(organization.id, owner_team.id, u_type)
end
OrganizationUser.build(organization.id, user.id)
TeamUser.build(organization.id, user.id, owner_team.id)
end
def create_gitea_org
@gitea_organization = Gitea::Organization::CreateService.call(@organization.gitea_token, organization)
end
def sync_gitea_info
organization.update!(gitea_uid: gitea_organization["id"])
owner_team.update!(gtid: gitea_organization["owner_team"]["id"])
end
end

View File

@ -0,0 +1,75 @@
class Organizations::Teams::CreateService < ApplicationService
attr_reader :user, :org, :params
attr_accessor :team, :gitea_team
def initialize(user, org, params)
@user = user
@org = org
@params = params
end
def call
Rails.logger.info("######Team create_service begin######")
Rails.logger.info("######params #{params}######")
ActiveRecord::Base.transaction do
create_team
create_units
create_gitea_team
sync_team_gtid
team.setup_team_project!
end
Rails.logger.info("######Team create_service end######")
team
end
private
def name
params[:name]
end
def description
params[:description]
end
def authorize
params[:authorize].present? ? params[:authorize] : "common"
end
def includes_all_project
params[:includes_all_project].present? ? params[:includes_all_project] : false
end
def can_create_org_project
params[:can_create_org_project].present? ? params[:can_create_org_project] : false
end
def create_team
@team = Team.build(org.id, name, description, authorize,
includes_all_project, can_create_org_project)
end
def units_params
%w(admin owner).include?(authorize) ? %w(code issues pulls releases) : params[:unit_types]
end
def create_units
return if units_params.blank?
begin
units_params.each do |unit|
TeamUnit.build(org.id, team.id, unit)
end
rescue
raise ActiveRecord::Rollback, "TeamUnit create error"
end
end
def create_gitea_team
@gitea_team = Gitea::Organization::Team::CreateService.call(org.gitea_token, org, team)
end
def sync_team_gtid
team.update!(gtid: gitea_team["id"])
end
end

View File

@ -0,0 +1,59 @@
class Organizations::Teams::UpdateService < ApplicationService
attr_reader :user, :team, :params
def initialize(user, team, params)
@user = user
@team = team
@params = params
end
def call
Rails.logger.info("######Team update_service begin######")
Rails.logger.info("######params #{params}######")
ActiveRecord::Base.transaction do
update_team(update_params)
update_units
team.reload
team.setup_team_project!
update_gitea_team
end
Rails.logger.info("######Team update_service end######")
team
end
private
def update_params
if team.authorize == "owner"
update_params = params.slice(:description)
else
update_params = params.slice(:name, :description, :authorize, :includes_all_project, :can_create_org_project)
end
update_params
end
def units_params
%w(admin owner).include?(team.authorize) ? %w(code issues pulls releases) : params[:unit_types]
end
def update_team(update_params)
team.update_attributes!(update_params)
end
def update_units
return if units_params.blank?
begin
team.team_units.map{|u|u.destroy!}
Rails.logger.info units_params
units_params.each do |unit|
TeamUnit.build(team&.organization&.id, team.id, unit)
end
rescue
raise ActiveRecord::Rollback, "TeamUnit update error"
end
end
def update_gitea_team
Gitea::Organization::Team::UpdateService.call(team&.organization&.gitea_token, team)
end
end

View File

@ -20,7 +20,7 @@ class Projects::ForkService < ApplicationService
clone_project.save!
new_repository = clone_project.repository
new_repository.user = @target_owner
new_repository.owner = @target_owner
new_repository.identifier = @project.identifier
new_repository.save!

View File

@ -0,0 +1,45 @@
class Projects::TransferService < ApplicationService
attr_accessor :project, :owner, :new_owner
def initialize(project, new_owner)
@project = project
@owner = project.owner
@new_owner = new_owner
end
def call
Rails.logger.info("###### Project transfer_service begin ######")
ActiveRecord::Base.transaction do
gitea_update_owner
update_owner
update_visit_teams
end
Rails.logger.info("##### Project transfer_service end ######")
@project.reload
end
private
def update_owner
project.update!(user_id: new_owner.id)
end
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
else
project.team_projects.each(&:destroy!)
end
end
def gitea_update_owner
begin
Gitea::Repository::TransferService.call(owner&.gitea_token, owner&.login, project.identifier, new_owner&.login)
rescue Exception => e
Rails.logger.info("##### Project transfer_service, gitea transfer error #{e}")
end
end
end

View File

@ -1,5 +1,6 @@
class Repositories::CreateService < ApplicationService
attr_reader :user, :project, :params
attr_accessor :repository, :gitea_repository
def initialize(user, project, params)
@project = project
@ -10,10 +11,10 @@ class Repositories::CreateService < ApplicationService
def call
@repository = Repository.new(repository_params)
ActiveRecord::Base.transaction do
if @repository.save!
gitea_repository = Gitea::Repository::CreateService.new(user.gitea_token, gitea_repository_params).call
sync_project(@repository, gitea_repository)
sync_repository(@repository, gitea_repository)
if repository.save!
create_gitea_repository
sync_project
sync_repository
# if project.project_type == "common"
# chain_params = {
# type: "create",
@ -29,7 +30,7 @@ class Repositories::CreateService < ApplicationService
else
Rails.logger.info("#############___________create_repository_erros______###########{@repository.errors.messages}")
end
@repository
repository
end
rescue => e
puts "create repository service error: #{e.message}"
@ -38,7 +39,25 @@ class Repositories::CreateService < ApplicationService
private
def sync_project(repository, gitea_repository)
def create_gitea_repository
if project.owner.is_a?(User)
@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
if gitea_repository
project.update_columns(
gpid: gitea_repository["id"],
@ -47,7 +66,7 @@ class Repositories::CreateService < ApplicationService
end
end
def sync_repository(repository, gitea_repository)
def sync_repository
repository.update_columns(url: remote_repository_url,) if gitea_repository
end

View File

@ -0,0 +1,8 @@
json.user_id user.id
json.login user.login
json.name user.full_name
json.mail user.mail
json.identity user.identity
json.watched current_user.watched?(user)
# json.email user.mail # 邮箱原则上不暴露的,如果实在需要的话只能对某些具体的接口公开
json.image_url url_to_avatar(user)

View File

@ -0,0 +1,7 @@
json.id org_user.id
json.user do
json.partial! "organizations/user_detail", user: org_user.user
end
json.team_names org_user.teams.pluck(:name)
json.created_at org_user.created_at.strftime("%Y-%m-%d")

View File

@ -0,0 +1,4 @@
json.total_count @organization_users.total_count
json.organization_users @organization_users do |org_user|
json.partial! "detail", org_user: org_user, organization: @organization
end

View File

@ -0,0 +1,13 @@
json.id organization.id
json.name organization.login
json.description organization.description
json.website organization.website
json.location organization.location
json.repo_admin_change_team_access organization.repo_admin_change_team_access
json.visibility organization.visibility
json.max_repo_creation organization.max_repo_creation
json.num_projects organization.num_projects
json.num_users organization.num_users
json.num_teams organization.num_teams
json.avatar_url url_to_avatar(organization)
json.created_at organization.created_on.strftime("%Y-%m-%d")

View File

@ -0,0 +1 @@
json.partial! "detail", organization: @organization

View File

@ -0,0 +1,4 @@
json.total_count @organizations.total_count
json.organizations @organizations do |organization|
json.partial! "detail", organization: organization
end

View File

@ -0,0 +1,4 @@
json.partial! "detail", organization: @organization
json.can_create_project @can_create_project
json.is_admin @is_admin
json.is_member @is_member

View File

@ -0,0 +1 @@
json.partial! "detail", organization: @organization

View File

@ -0,0 +1,9 @@
json.total_count @projects.total_count
json.projects @projects.each do |project|
json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id)
json.mirror_url project.repository&.mirror_url
json.type project.numerical_for_project_type
json.praised project.praised_by?(current_user)
json.last_update_time render_unix_time(project.updated_on)
json.time_ago time_from_now(project.updated_on)
end

View File

@ -0,0 +1,9 @@
json.total_count @projects.size
json.projects @projects.each do |project|
json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id)
json.mirror_url project.repository&.mirror_url
json.type project.numerical_for_project_type
json.praised project.praised_by?(current_user)
json.last_update_time render_unix_time(project.updated_on)
json.time_ago time_from_now(project.updated_on)
end

View File

@ -0,0 +1,7 @@
json.id team_project.id
json.project do
json.owner_name team_project&.project&.owner&.login
json.owner_image_url url_to_avatar(team_project&.project&.owner)
json.name team_project&.project&.name
json.identifier team_project&.project&.identifier
end

View File

@ -0,0 +1 @@
json.partial! "detail", team_project: @team_project, team: @team, organization: @organization

View File

@ -0,0 +1,4 @@
json.total_count @team_projects.total_count
json.team_projects @team_projects do |team_project|
json.partial! "detail", team_project: team_project, team: @team, organization: @organization
end

View File

@ -0,0 +1,5 @@
json.id team_user.id
json.user do
json.partial! "organizations/user_detail", user: team_user.user
end
json.created_at team_user.created_at.strftime("%Y-%m-%d")

View File

@ -0,0 +1 @@
json.partial! "detail", team_user: @team_user, team: @team, organization: @organization

View File

@ -0,0 +1,4 @@
json.total_count @team_users.total_count
json.team_users @team_users do |team_user|
json.partial! "detail", team_user: team_user, team: @team, organization: @organization
end

View File

@ -0,0 +1,14 @@
json.id team.id
json.name team.name
json.description team.description
json.authorize team.authorize
json.includes_all_project team.includes_all_project
json.can_create_org_project team.can_create_org_project
json.num_projects team.num_projects
json.num_users team.num_users
json.units team.team_units.pluck(:unit_type)
json.users team.team_users.each do |user|
json.partial! "organizations/user_detail", user: user&.user
end
json.is_admin @is_admin
json.is_member team.is_member?(current_user.id)

View File

@ -0,0 +1 @@
json.partial! "detail", team: @team, organization: @organization

View File

@ -0,0 +1,4 @@
json.total_count @teams.total_count
json.teams @teams do |team|
json.partial! "detail", team: team, organization: @organization
end

View File

@ -0,0 +1,4 @@
json.total_count @teams.size
json.teams @teams do |team|
json.partial! "detail", team: team, organization: @organization
end

View File

@ -0,0 +1,3 @@
json.partial! "detail", team: @team, organization: @organization
json.is_admin @is_admin
json.is_member @is_member

View File

@ -0,0 +1 @@
json.partial! "detail", team: @team, organization: @organization

View File

@ -0,0 +1,7 @@
json.total_count @owners.size
json.owners @owners.each do |owner|
json.id owner.id
json.type owner.type
json.name owner.login
json.avatar_url url_to_avatar(owner)
end

View File

@ -18,11 +18,13 @@ json.author do
if project.educoder?
project_educoder = project.project_educoder
json.name project_educoder&.owner
json.type 'Educoder'
json.login project_educoder&.repo_name.split('/')[0]
json.image_url render_educoder_avatar_url(project.project_educoder)
else
user = project.owner
json.name user.try(:show_real_name)
json.type user&.type
json.login user.login
json.image_url render_avatar_url(user)
end

View File

@ -0,0 +1,5 @@
json.total_count @teams.total_count
json.teams @teams.each do |team|
json.(team, :id, :name, :authorize)
json.can_remove !team.includes_all_project && team&.organization&.repo_admin_change_team_access
end

View File

@ -1,11 +1,13 @@
json.author do
if @project.forge?
json.login user.login
json.type user&.type
json.name user.real_name
json.image_url url_to_avatar(user)
else
json.login @project.project_educoder&.repo_name&.split('/')[0]
json.name @project.project_educoder&.owner
json.type 'Educoder'
json.image_url @project.project_educoder&.image_url
end
end

View File

@ -0,0 +1,4 @@
json.total_count @organizations.total_count
json.organizations @organizations do |organization|
json.partial! "/organizations/organizations/detail", organization: organization
end

View File

@ -12,6 +12,7 @@ json.watched_count @user.fan_count #粉丝
json.watching_count @user.follow_count #关注数
json.undo_events @undo_events
json.user_composes_count @user_composes_count
json.user_org_count @user_org_count
json.common_projects_count @projects_common_count
json.mirror_projects_count @projects_mirrior_count
json.sync_mirror_projects_count @projects_sync_mirrior_count

View File

@ -61,6 +61,8 @@ zh-CN:
close_issue: 工单
activerecord:
attributes:
organization:
login: '组织名称'
user:
login: '登录名'
lastname: '姓名'

View File

@ -103,6 +103,34 @@ Rails.application.routes.draw do
put 'commons/unhidden', to: 'commons#unhidden'
delete 'commons/delete', to: 'commons#delete'
resources :owners, only: [:index]
scope module: :organizations do
resources :organizations, except: [:edit, :new] do
resources :organization_users, only: [:index, :destroy] do
collection do
delete :quit
end
end
resources :teams, except: [:edit, :new] do
collection do
get :search
end
resources :team_users, only: [:index, :create, :destroy] do
collection do
delete :quit
end
end
resources :team_projects, only: [:index, :create, :destroy] do ;end
end
resources :projects, only: [:index] do
collection do
get :search
end
end
end
end
resources :issues, except: [:index, :new,:create, :update, :edit, :destroy] do
resources :journals, only: [:index, :create, :destroy, :edit, :update] do
member do
@ -225,6 +253,7 @@ Rails.application.routes.draw do
end
scope module: :users do
resources :organizations, only: [:index]
# resources :projects, only: [:index]
# resources :subjects, only: [:index]
resources :project_packages, only: [:index]
@ -493,6 +522,7 @@ Rails.application.routes.draw do
end
scope module: :projects do
resources :teams, only: [:index, :create, :destroy]
scope do
get(
'/blob/*id/diff',

View File

@ -0,0 +1,10 @@
class CreateOrganizationUsers < ActiveRecord::Migration[5.2]
def change
create_table :organization_users do |t|
t.references :user
t.references :organization
t.timestamps
end
end
end

View File

@ -0,0 +1,15 @@
class CreateOrganizationExtensions < ActiveRecord::Migration[5.2]
def change
create_table :organization_extensions do |t|
t.references :organization
t.string :description, comment: "组织描述"
t.string :website, comment: "组织官方网站"
t.string :location, comment: "组织地区"
t.boolean :repo_admin_change_team_access, comment: "项目管理员是否可以添加或移除团队的访问权限", default: false
t.integer :visibility, comment: "组织可见性", default: 0
t.integer :max_repo_creation, comment: "组织最大仓库数", default: -1
t.timestamps
end
end
end

View File

@ -0,0 +1,17 @@
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.references :organization
t.string :name, comment: "团队名称"
t.string :description, comment: "团队描述"
t.integer :authorize, comment: "团队权限", default: 0
t.integer :num_projects, comment: "团队项目数量", default: 0
t.integer :num_users, comment: "团队成员数量", default: 0
t.boolean :includes_all_project, comment: "团队是否拥有所有项目", default: false
t.boolean :can_create_org_project, comment: "团队是否能创建项目", default: false
t.integer :gtid, comment: "团队在gitea里的id"
t.timestamps
end
end
end

View File

@ -0,0 +1,11 @@
class CreateTeamProjects < ActiveRecord::Migration[5.2]
def change
create_table :team_projects do |t|
t.references :organization
t.references :project
t.references :team
t.timestamps
end
end
end

View File

@ -0,0 +1,10 @@
class CreateTeamUsers < ActiveRecord::Migration[5.2]
def change
create_table :team_users do |t|
t.references :organization
t.references :team
t.references :user
t.timestamps
end
end
end

View File

@ -0,0 +1,11 @@
class CreateTeamUnits < ActiveRecord::Migration[5.2]
def change
create_table :team_units do |t|
t.references :organization
t.references :team
t.integer :unit_type, comment: "访问单元类型"
t.timestamps
end
end
end

View File

@ -0,0 +1,13 @@
class AddColumnsToOrganizationExtension < ActiveRecord::Migration[5.2]
def change
add_column :organization_extensions, :num_projects, :integer, default: 0
add_column :organization_extensions, :num_users, :integer, default: 0
add_column :organization_extensions, :num_teams, :integer, default: 0
OrganizationExtension.find_each do |e|
OrganizationExtension.reset_counters(e.id, :organization_users)
OrganizationExtension.reset_counters(e.id, :projects)
OrganizationExtension.reset_counters(e.id, :teams)
end
end
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe OrganizationExtension, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe OrganizationUser, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe TeamProject, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

5
spec/models/team_spec.rb Normal file
View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Team, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe TeamUnit, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe TeamUser, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end