| @@ -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 | |||
| @@ -113,6 +113,8 @@ http://localhost:3000/ | |||
| ### API | |||
| - [API](api_document.md) | |||
| - [API](showdoc.com.cn) | |||
| 账号:forgeplus@admin.com 密码:forge123 | |||
| ## 贡献代码 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -124,7 +124,7 @@ class VersionReleasesController < ApplicationController | |||
| private | |||
| def set_user | |||
| @user = @repository.user | |||
| @user = @repository.owner | |||
| end | |||
| def find_version | |||
| @@ -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 | |||
| @@ -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}" | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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? | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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! | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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") | |||
| @@ -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 | |||
| @@ -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") | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "detail", organization: @organization | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @organizations.total_count | |||
| json.organizations @organizations do |organization| | |||
| json.partial! "detail", organization: organization | |||
| end | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "detail", organization: @organization | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "detail", team_project: @team_project, team: @team, organization: @organization | |||
| @@ -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 | |||
| @@ -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") | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "detail", team_user: @team_user, team: @team, organization: @organization | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "detail", team: @team, organization: @organization | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @teams.total_count | |||
| json.teams @teams do |team| | |||
| json.partial! "detail", team: team, organization: @organization | |||
| end | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @teams.size | |||
| json.teams @teams do |team| | |||
| json.partial! "detail", team: team, organization: @organization | |||
| end | |||
| @@ -0,0 +1,3 @@ | |||
| json.partial! "detail", team: @team, organization: @organization | |||
| json.is_admin @is_admin | |||
| json.is_member @is_member | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "detail", team: @team, organization: @organization | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @organizations.total_count | |||
| json.organizations @organizations do |organization| | |||
| json.partial! "/organizations/organizations/detail", organization: organization | |||
| end | |||
| @@ -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 | |||
| @@ -61,6 +61,8 @@ zh-CN: | |||
| close_issue: 工单 | |||
| activerecord: | |||
| attributes: | |||
| organization: | |||
| login: '组织名称' | |||
| user: | |||
| login: '登录名' | |||
| lastname: '姓名' | |||
| @@ -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', | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe OrganizationExtension, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe OrganizationUser, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe TeamProject, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe Team, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe TeamUnit, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| require 'rails_helper' | |||
| RSpec.describe TeamUser, type: :model do | |||
| pending "add some examples to (or delete) #{__FILE__}" | |||
| end | |||