diff --git a/.gitignore b/.gitignore index 77104d20..6b4fd25c 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ public/react/yarn.lock /.idea/* # Ignore react node_modules +public/system/* public/react/* /public/react/.cache /public/react/node_modules/ @@ -82,4 +83,5 @@ docker/ educoder.sql redis_data/ Dockerfile -dump.rdb \ No newline at end of file +dump.rdb +.tags* diff --git a/CHANGELOG.md b/CHANGELOG.md index 4345e60b..d24953c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # Changelog +## [v3.0.4](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-24 + +* BUGFIXES + * Fix 在线修改文件,页面文件显不及时的问题(46049) + * Fix Fork项目,接口多次调用问题(45052) + * FIX 页面置顶功能区域排版问题(45825) + * Fix 其他样式显示问题 + +* ENHANCEMENTS + * ADD 合并请求页面显示有冲突文件状态(46016) + * ADD 创建组织各属性添加规则匹配功能(45707) + * ADD 微信分享功能(45707) + +## [v3.0.3](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-08 + +* BUGFIXES + * Fix 解决易修标题过长导致的排版问题(45469) + * Fix 解决合并请求详情页面排版错误的问题(45457) + * FIX 解决转移仓库界面专有名词描述错误的问题(45455) + * Fix 解决markdown格式文件自动生成数字排序的问题(45454) + * Fix 解决镜像项目源地址不显示的问题(45403) + * Fix 解决镜像项目导航显示错误问题(45398) + * Fix 解决其他相关bug + +* ENHANCEMENTS + * UPDATE 用户注册时,账号和密码正则匹配调整(45336) (45318) (45290) + * ADD 创建组织各属性添加规则匹配功能(45313) (45289) + * ADD 创建团建各属性添加规则匹配功能(45334) (45325) (45287) + * ADD 仓库转移功能(45017) (45015) + +## [v3.0.2](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-04-23 + +* BUGFIXES + * Fix 解决部分用户头像不显示问题 + * Fix 解决代码库模块中最左侧目录中的文件定位加载不准确的问题 + * FIX 解决团队管理页面中项目链接错误问题 + * Fix 解决markdown格式文件显示问题 + * Fix 解决组织名下创建项目报错的问题 + * Fix 解决组织名下的项目,创建issue报错的问题 + * Fix 解决组织名下创建团队提示信息信息显示错误问题 + * Fix 解决点击组织图片时,链接加载错误问题 + * Fix 修复查询版本库信息安全漏洞 + * Fix 解决修复团队成员操作访问组织仓库报403错误的问题 + * Fix 解决owners团队成员对仓库添加成功失败的问题 + +* ENHANCEMENTS + * ADD 自动生产用户头像功能 + * ADD 创建组织支持中文名称 + * ADD 创建团建支持中文名称 + * ADD 组织名称统一显示中文名 + * ADD 团队名称统一显示中文名 + * ADD 用户头像悬浮时展示相关信息 + * ADD 项目详情页添加实践课程链接入口 + * ADD README文件页面添加添加目录导航功能 + * UPDATE 升级改版底部footer信息 + * UPDATE 升级用户操作版本库权限 + ## [v3.0.1](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-03-19 * BUGFIXES diff --git a/Gemfile b/Gemfile index 281dd705..713eb860 100644 --- a/Gemfile +++ b/Gemfile @@ -126,3 +126,5 @@ gem 'request_store' gem 'harmonious_dictionary', '~> 0.0.1' gem 'parallel', '~> 1.19', '>= 1.19.1' + +gem 'letter_avatar' diff --git a/Gemfile.lock b/Gemfile.lock index cbd598f5..015c0a51 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -162,6 +162,7 @@ GEM activerecord kaminari-core (= 1.2.0) kaminari-core (1.2.0) + letter_avatar (0.3.8) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -456,6 +457,7 @@ DEPENDENCIES jbuilder (~> 2.5) jquery-rails kaminari (~> 1.1, >= 1.1.1) + letter_avatar listen (>= 3.0.5, < 3.2) mysql2 (>= 0.4.4, < 0.6.0) oauth2 diff --git a/README.md b/README.md index 2a607e2a..1bcd05cc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Trustie (确实)是一个以大众化协同开发、开放式资源共享、 * Redis 5+ -* NodeJS > 13.0.0 +* imagemagick ### Steps @@ -65,7 +65,7 @@ default: &default **因目前gitea平台api受限,暂时推荐从forge平台获取gitea部署文件进行部署:https://forgeplus.trustie.net/projects/Trustie/gitea-binary** -#### 配置gitea服务步骤 +**配置gitea服务步骤** 1. 部署gitea服务,并注册root账户 2. 修改forge平台的 config/configuration.yml中的gitea服务指向地址,如: @@ -80,60 +80,64 @@ gitea: #### 6. 安装redis环境 **请自行搜索各平台如何安装部署redis环境** +#### 7. 安装imagemagick插件 +- Mac OS X +```bash + brew install imagemagick ghostscript +``` -#### 7. 创建数据库 +- Linux +```bash +sudo apt-get install -y imagemagick +``` + +#### 8. 创建数据库 **开发环境为development, 生成环境为production** ```bash rails db:create RAILS_ENV=development ``` -#### 8. 导入数据表结构 +#### 9. 导入数据表结构 ```bash bundle exec rake sync_table_structure:import_csv ``` -#### 9. 执行migrate迁移文件 +#### 10. 执行migrate迁移文件 **开发环境为development, 生成环境为production** ```bash rails db:migrate RAILS_ENV=development ``` -#### 10. clone前端代码 +#### 11. clone前端代码 **将前端代码克隆到public/react目录下,目录结构应该是: public/react/build** ```bash git clone -b standalone https://git.trustie.net/jasder/build.git ``` -#### 11. 启动redis(此处已mac系统为例) +#### 12. 启动redis(此处已mac系统为例) ```bash redis-server& ``` -#### 12. 启动sidekiq +#### 13. 启动sidekiq **开发环境为development, 生成环境为production** ```bash bundle exec sidekiq -C config/sidekiq.yml -e production -d ``` -#### 13. 启动rails服务 +#### 14. 启动rails服务 ```bash rails s ``` -#### 14. 浏览器访问 -在浏览器中输入如下地址访问: -```bash -http://localhost:3000/ -``` - #### 15. 浏览器访问 在浏览器中输入如下地址访问: ```bash http://localhost:3000/ ``` -#### 15. 其他说明 +#### 16. 其他说明 通过页面注册都第一个用户为平台管理员用户 ## 页面展示 diff --git a/app/assets/javascripts/admins/faqs.js b/app/assets/javascripts/admins/faqs.js new file mode 100644 index 00000000..dee720fa --- /dev/null +++ b/app/assets/javascripts/admins/faqs.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/helps/faqs.js b/app/assets/javascripts/helps/faqs.js new file mode 100644 index 00000000..dee720fa --- /dev/null +++ b/app/assets/javascripts/helps/faqs.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/stylesheets/admins/faqs.scss b/app/assets/stylesheets/admins/faqs.scss new file mode 100644 index 00000000..8d1a08d3 --- /dev/null +++ b/app/assets/stylesheets/admins/faqs.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the admins/faqs controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/helps/faqs.scss b/app/assets/stylesheets/helps/faqs.scss new file mode 100644 index 00000000..4d3d2bc4 --- /dev/null +++ b/app/assets/stylesheets/helps/faqs.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the helps/faqs controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 0166ba43..3d002524 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -183,6 +183,7 @@ class AccountsController < ApplicationController # 用户登录 def login + Users::LoginForm.new(account_params).validate! @user = User.try_to_login(params[:login], params[:password]) return normal_status(-2, "错误的账号或密码") if @user.blank? @@ -363,4 +364,7 @@ class AccountsController < ApplicationController params.require(:user).permit(:login, :email, :phone) end + def account_params + params.require(:account).permit(:login, :password) + end end diff --git a/app/controllers/admins/faqs_controller.rb b/app/controllers/admins/faqs_controller.rb new file mode 100644 index 00000000..fc00f847 --- /dev/null +++ b/app/controllers/admins/faqs_controller.rb @@ -0,0 +1,58 @@ +class Admins::FaqsController < Admins::BaseController + before_action :find_faq, only: [:edit,:update, :destroy] + + def index + sort_by = params[:sort_by] ||= 'updated_at' + sort_direction = params[:sort_direction] ||= 'desc' + + keyword = params[:keyword].to_s.strip + collection = Faq.search_question(keyword).order("#{sort_by} #{sort_direction}") + @faqs = paginate collection + end + + def new + @faq = Faq.new + end + + def edit + end + + def update + begin + @faq.update!(faq_params) + flash[:success] = '修改成功' + rescue Exception + flash[:danger] = @faq.errors.full_messages.to_sentence + end + + redirect_to admins_faqs_path + end + + def destroy + @faq.destroy + + redirect_to admins_faqs_path + flash[:success] = "删除成功" + end + + def create + @faq = Faq.new(faq_params) + begin + @faq.save! + flash[:success] = '创建成功' + rescue Exception + flash[:danger] = @faq.errors.full_messages.to_sentence + end + redirect_to admins_faqs_path + end + + private + def find_faq + @faq = Faq.find params[:id] + end + + def faq_params + params.require(:faq).permit(:question, :url) + end + +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ee81765a..6875b815 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z) OPENKEY = "79e33abd4b6588941ab7622aed1e67e8" - helper_method :current_user + helper_method :current_user, :base_url # 所有请求必须合法签名 def check_sign @@ -770,6 +770,11 @@ class ApplicationController < ActionController::Base @repository ||= load_project&.repository end + def base_url + request.base_url + end + + private def object_not_found uid_logger("Missing template or cant't find record, responding with 404") diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/controllers/concerns/acceleratorable.rb b/app/controllers/concerns/acceleratorable.rb new file mode 100644 index 00000000..5243ac53 --- /dev/null +++ b/app/controllers/concerns/acceleratorable.rb @@ -0,0 +1,36 @@ +module Acceleratorable + extend ActiveSupport::Concern + + def enable_accelerator?(clone_addr) + is_foreign_url?(clone_addr) && config_accelerator? + end + + def accelerator_url(repo_name) + [accelerator_domain, accelerator_username, "#{repo_name}.git"].join('/') + end + + def github_domain + 'github.com' + end + + def gitlab_domain + 'gitlab.com' + end + + def accelerator_domain + Gitea.gitea_config[:accelerator]["domain"] + end + + def accelerator_username + Gitea.gitea_config[:accelerator]["access_key_id"] + end + + def config_accelerator? + Gitea.gitea_config[:accelerator].present? + end + + def is_foreign_url?(clone_addr) + clone_addr.include?(github_domain) || clone_addr.include?(gitlab_domain) + end + +end \ No newline at end of file diff --git a/app/controllers/concerns/register_helper.rb b/app/controllers/concerns/register_helper.rb index 1584067a..2b16b67f 100644 --- a/app/controllers/concerns/register_helper.rb +++ b/app/controllers/concerns/register_helper.rb @@ -21,6 +21,7 @@ module RegisterHelper result = Gitea::User::GenerateTokenService.call(username, password) forge_user.gitea_token = result['sha1'] forge_user.gitea_uid = gitea_user[:body]['id'] + forge_user.mail = email if forge_user.save UserExtension.create!(user_id: forge_user.id) unless forge_user.user_extension.blank? result[:user] = {id: forge_user.id, token: forge_user.gitea_token} diff --git a/app/controllers/helps/faqs_controller.rb b/app/controllers/helps/faqs_controller.rb new file mode 100644 index 00000000..a18f4927 --- /dev/null +++ b/app/controllers/helps/faqs_controller.rb @@ -0,0 +1,8 @@ +class Helps::FaqsController < ApplicationController + skip_before_action :check_sign, :user_setup + + def index + faqs = Faq.select_without_id.order(updated_at: :desc) + render json: faqs.as_json(:except => [:id]) + end +end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index a3b5fe92..3584d6bf 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -101,51 +101,46 @@ class IssuesController < ApplicationController end def create - if params[:subject].blank? - normal_status(-1, "标题不能为空") - elsif params[:subject].to_s.size > 255 - normal_status(-1, "标题不能超过255个字符") - else - issue_params = issue_send_params(params) - - @issue = Issue.new(issue_params) - if @issue.save! - if params[:attachment_ids].present? - params[:attachment_ids].each do |id| - attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) - unless attachment.blank? - attachment.container = @issue - attachment.author_id = current_user.id - attachment.description = "" - attachment.save - end + issue_params = issue_send_params(params) + Issues::CreateForm.new({subject:issue_params[:subject]}).validate! + @issue = Issue.new(issue_params) + if @issue.save! + if params[:attachment_ids].present? + params[:attachment_ids].each do |id| + attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) + unless attachment.blank? + attachment.container = @issue + attachment.author_id = current_user.id + attachment.description = "" + attachment.save end end - if params[:issue_tag_ids].present? - params[:issue_tag_ids].each do |tag| - IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) - end + end + if params[:issue_tag_ids].present? + params[:issue_tag_ids].each do |tag| + IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) end - if params[:assigned_to_id].present? - Tiding.create!(user_id: params[:assigned_to_id], trigger_user_id: current_user.id, - container_id: @issue.id, container_type: 'Issue', - parent_container_id: @project.id, parent_container_type: "Project", - tiding_type: 'issue', status: 0) - end - - #为悬赏任务时, 扣除当前用户的积分 - if params[:issue_type].to_s == "2" - post_to_chain("minus", params[:token].to_i, current_user.try(:login)) - end - - @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") - render json: {status: 0, message: "创建成", id: @issue.id} - else - normal_status(-1, "创建失败") + end + if params[:assigned_to_id].present? + Tiding.create!(user_id: params[:assigned_to_id], trigger_user_id: current_user.id, + container_id: @issue.id, container_type: 'Issue', + parent_container_id: @project.id, parent_container_type: "Project", + tiding_type: 'issue', status: 0) end - end + #为悬赏任务时, 扣除当前用户的积分 + if params[:issue_type].to_s == "2" + post_to_chain("minus", params[:token].to_i, current_user.try(:login)) + end + @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") + render json: {status: 0, message: "创建成", id: @issue.id} + else + normal_status(-1, "创建失败") + end + rescue Exception => exception + puts exception.message + normal_status(-1, exception.message) end def edit @@ -199,7 +194,7 @@ class IssuesController < ApplicationController normal_status(-1, "不允许修改为关闭状态") else issue_params = issue_send_params(params).except(:issue_classify, :author_id, :project_id) - + Issues::UpdateForm.new({subject:issue_params[:subject]}).validate! if @issue.update_attributes(issue_params) if params[:status_id].to_i == 5 #任务由非关闭状态到关闭状态时 @issue.issue_times.update_all(end_time: Time.now) @@ -225,6 +220,9 @@ class IssuesController < ApplicationController normal_status(-1, "更新失败") end end + rescue Exception => exception + puts exception.message + normal_status(-1, exception.message) end def show diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 18ab03d5..1a62bc29 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -56,7 +56,7 @@ class MembersController < ApplicationController end def member_exists? - @project.member?(params[:user_id]) + @project.members.exists?(params[:user_id]) end def operate! @@ -68,6 +68,6 @@ class MembersController < ApplicationController end def check_member_not_exists! - return render_result(1, "user_id为#{params[:user_id]}的用户还不是项目成员") unless member_exists? + return render_result(1, "user_id为#{params[:user_id]}的用户还不是项目成员") unless @project.member?(params[:user_id]) end end diff --git a/app/controllers/organizations/base_controller.rb b/app/controllers/organizations/base_controller.rb index 2e8ae0cf..e18ab9ae 100644 --- a/app/controllers/organizations/base_controller.rb +++ b/app/controllers/organizations/base_controller.rb @@ -17,11 +17,12 @@ class Organizations::BaseController < ApplicationController end def org_privacy_condition + return false if current_user.admin? @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) + !current_user&.admin? && @team.team_users.where(user_id: current_user.id).blank? && !@organization.is_owner?(current_user.id) end def user_mark @@ -31,4 +32,4 @@ class Organizations::BaseController < ApplicationController def project_mark params[:repo_name] || params[:id] end -end \ No newline at end of file +end diff --git a/app/controllers/organizations/organizations_controller.rb b/app/controllers/organizations/organizations_controller.rb index 73335cf1..29cb15a6 100644 --- a/app/controllers/organizations/organizations_controller.rb +++ b/app/controllers/organizations/organizations_controller.rb @@ -1,5 +1,5 @@ class Organizations::OrganizationsController < Organizations::BaseController - before_action :require_login, except: [:index, :show] + before_action :require_login, except: [:index, :show, :recommend] 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] @@ -25,6 +25,7 @@ class Organizations::OrganizationsController < Organizations::BaseController def create ActiveRecord::Base.transaction do + Organizations::CreateForm.new(organization_params).validate! @organization = Organizations::CreateService.call(current_user, organization_params) Util.write_file(@image, avatar_path(@organization)) if params[:image].present? end @@ -35,6 +36,7 @@ class Organizations::OrganizationsController < Organizations::BaseController def update ActiveRecord::Base.transaction do + Organizations::CreateForm.new(organization_params).validate! login = @organization.login @organization.login = organization_params[:name] if organization_params[:name].present? @organization.nickname = organization_params[:nickname] if organization_params[:nickname].present? @@ -60,6 +62,13 @@ class Organizations::OrganizationsController < Organizations::BaseController tip_exception(e.message) end + def recommend + recommend = %W(xuos Huawei_Technology openatom_foundation pkecosystem TensorLayer) + + @organizations = Organization.with_visibility(%w(common)) + .where(login: recommend).select(:id, :login, :firstname, :lastname, :nickname) + end + private def convert_image! return unless params[:image].present? diff --git a/app/controllers/organizations/teams_controller.rb b/app/controllers/organizations/teams_controller.rb index 5bc01fe3..c599ac42 100644 --- a/app/controllers/organizations/teams_controller.rb +++ b/app/controllers/organizations/teams_controller.rb @@ -33,13 +33,17 @@ class Organizations::TeamsController < Organizations::BaseController end def create - @team = Organizations::Teams::CreateService.call(current_user, @organization, team_params) + ActiveRecord::Base.transaction do + Organizations::CreateTeamForm.new(team_params).validate! + @team = Organizations::Teams::CreateService.call(current_user, @organization, team_params) + end rescue Exception => e uid_logger_error(e.message) tip_exception(e.message) end def update + Organizations::CreateTeamForm.new(team_params).validate! @team = Organizations::Teams::UpdateService.call(current_user, @team, team_params) rescue Exception => e uid_logger_error(e.message) @@ -60,7 +64,7 @@ class Organizations::TeamsController < Organizations::BaseController private def team_params - params.permit(:name, :description, :authorize, :includes_all_project, :can_create_org_project, :unit_types => []) + params.permit(:name, :nickname, :description, :authorize, :includes_all_project, :can_create_org_project, :unit_types => []) end def load_organization diff --git a/app/controllers/projects/applied_transfer_projects_controller.rb b/app/controllers/projects/applied_transfer_projects_controller.rb new file mode 100644 index 00000000..e62033f2 --- /dev/null +++ b/app/controllers/projects/applied_transfer_projects_controller.rb @@ -0,0 +1,26 @@ +class Projects::AppliedTransferProjectsController < Projects::BaseController + before_action :check_auth + + def organizations + @organizations = Organization.includes(:organization_extension).joins(team_users: :team).where(team_users: {user_id: current_user.id}, teams: {authorize: %w(admin owner)}) + end + + def create + @applied_transfer_project = Projects::ApplyTransferService.call(current_user, @project, params) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def cancel + @applied_transfer_project = Projects::CancelTransferService.call(current_user, @project) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + private + def check_auth + return render_forbidden unless current_user.admin? ||@project.owner?(current_user) + end +end \ No newline at end of file diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 378b4ebd..b748123f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -2,6 +2,8 @@ class ProjectsController < ApplicationController include ApplicationHelper include OperateProjectAbilityAble include ProjectsHelper + include Acceleratorable + before_action :require_login, except: %i[index branches group_type_list simple show fork_users praise_users watch_users recommend about menu_list] before_action :load_project, except: %i[index group_type_list migrate create recommend] before_action :authorizate_user_can_edit_project!, only: %i[update] @@ -60,7 +62,23 @@ class ProjectsController < ApplicationController def migrate Projects::MigrateForm.new(mirror_params).validate! - @project = Projects::MigrateService.new(current_user, mirror_params).call + + @project = + if enable_accelerator?(mirror_params[:clone_addr]) + source_clone_url = mirror_params[:clone_addr] + uid_logger("########## 已动加速器 ##########") + result = Gitea::Accelerator::MigrateService.call(mirror_params) + if result[:status] == :success + Rails.logger.info "########## 加速镜像成功 ########## " + Projects::MigrateService.call(current_user, + mirror_params.merge(source_clone_url: source_clone_url, + clone_addr: accelerator_url(mirror_params[:repository_name]))) + else + Projects::MigrateService.call(current_user, mirror_params) + end + else + Projects::MigrateService.call(current_user, mirror_params) + end rescue Exception => e uid_logger_error(e.message) tip_exception(e.message) @@ -95,19 +113,29 @@ class ProjectsController < ApplicationController def update ActiveRecord::Base.transaction do - # Projects::CreateForm.new(project_params).validate! - private = params[:private] || false + # TODO: + # 临时特殊处理修改website、lesson_url操作方法 + if project_params.has_key?("website") + @project.update(project_params) + else + validate_params = project_params.slice(:name, :description, + :project_category_id, :project_language_id, :private) + + Projects::UpdateForm.new(validate_params).validate! + + private = params[:private] || false - new_project_params = project_params.except(:private).merge(is_public: !private) - @project.update_attributes!(new_project_params) - gitea_params = { - private: private, - default_branch: @project.default_branch, - website: @project.website - } - if [true, false].include? private - Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params) - @project.repository.update_column(:hidden, private) + new_project_params = project_params.except(:private).merge(is_public: !private) + @project.update_attributes!(new_project_params) + gitea_params = { + private: private, + default_branch: @project.default_branch, + website: @project.website + } + if [true, false].include? private + Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params) + @project.repository.update_column(:hidden, private) + end end end rescue Exception => e @@ -156,7 +184,7 @@ class ProjectsController < ApplicationController end def recommend - @projects = Project.recommend.includes(:repository, :project_category, :owner).limit(5) + @projects = Project.recommend.includes(:repository, :project_category, :owner).order(id: :desc) end def about @@ -190,7 +218,7 @@ class ProjectsController < ApplicationController private def project_params - params.permit(:user_id, :name, :description, :repository_name, :website, + params.permit(:user_id, :name, :description, :repository_name, :website, :lesson_url, :project_category_id, :project_language_id, :license_id, :ignore_id, :private) end diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index 12663901..139ea952 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -136,11 +136,12 @@ class PullRequestsController < ApplicationController def show @issue_user = @issue.user @issue_assign_to = @issue.get_assign_user - + @gitea_pull = Gitea::PullRequest::GetService.call(@owner.login, + @repository.identifier, @pull_request.gpid, current_user&.gitea_token) end def pr_merge - return render_forbidden("你没有权限操作.") if @project.reporter?(current_user) + return render_forbidden("你没有权限操作.") unless @project.operator?(current_user) if params[:do].blank? normal_status(-1, "请选择合并方式") @@ -149,12 +150,12 @@ class PullRequestsController < ApplicationController begin result = PullRequests::MergeService.call(@owner, @repository, @pull_request, current_user, params) - if result && @pull_request.merge! + if result.status == 200 && @pull_request.merge! @pull_request.project_trend_status! @issue&.custom_journal_detail("merge", "", "该合并请求已被合并", current_user&.id) normal_status(1, "合并成功") else - normal_status(-1, "合并失败") + normal_status(-1, result.message) end rescue => e normal_status(-1, e.message) @@ -215,7 +216,7 @@ class PullRequestsController < ApplicationController def get_relatived @project_tags = @project.issue_tags&.select(:id,:name, :color).as_json @project_versions = @project.versions&.select(:id,:name, :status).as_json - @project_members = @project.members_user_infos + @project_members = @project.all_developers @project_priories = IssuePriority&.select(:id,:name, :position).as_json end diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 10b352ae..6594d06e 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -53,6 +53,18 @@ class RepositoriesController < ApplicationController @entries = Gitea::Repository::Entries::ListService.new(@owner, @project.identifier, ref: @ref).call @entries = @entries.present? ? @entries.sort_by{ |hash| hash['type'] } : [] @path = Gitea.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{@ref}/" + + # TODO + # 临时处理readme文件问题 + admin = current_user.blank? ? User.where(admin: true).last : current_user + + result = Gitea::Repository::Readme::GetService.call(@owner.login, @project.identifier, @ref, admin&.gitea_token) + @readme = + if result[:status] == :success + result[:body] + else + {} + end end end diff --git a/app/controllers/users/applied_messages_controller.rb b/app/controllers/users/applied_messages_controller.rb new file mode 100644 index 00000000..e80cabf9 --- /dev/null +++ b/app/controllers/users/applied_messages_controller.rb @@ -0,0 +1,18 @@ +class Users::AppliedMessagesController < Users::BaseController + before_action :check_auth + after_action :view_messages, only: [:index] + + def index + @applied_messages = @_observed_user.applied_messages.order(viewed: :asc, created_at: :desc) + @applied_messages = paginate @applied_messages + end + + private + def check_auth + return render_forbidden unless observed_logged_user? + end + + def view_messages + @applied_messages.update_all(viewed: 'viewed') + end +end \ No newline at end of file diff --git a/app/controllers/users/applied_transfer_projects_controller.rb b/app/controllers/users/applied_transfer_projects_controller.rb new file mode 100644 index 00000000..b1777f52 --- /dev/null +++ b/app/controllers/users/applied_transfer_projects_controller.rb @@ -0,0 +1,41 @@ +class Users::AppliedTransferProjectsController < Users::BaseController + before_action :check_auth + before_action :find_applied_transfer_project, except: [:index] + before_action :find_project, except: [:index] + + def index + user_collection_sql = AppliedTransferProject.where(owner_id: @_observed_user.id).to_sql + org_collection_sql = AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @_observed_user.id}, teams: {authorize: %w(admin owner)} )).to_sql + @applied_transfer_projects = AppliedTransferProject.from("( #{ user_collection_sql } UNION #{ org_collection_sql } ) AS applied_transfer_projects") + @applied_transfer_projects = paginate @applied_transfer_projects.order("created_at desc") + end + + # 接受迁移 + def accept + @applied_transfer_project = Projects::AcceptTransferService.call(current_user, @project) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + # 拒绝迁移 + def refuse + @applied_transfer_project = Projects::RefuseTransferService.call(current_user, @project) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + private + def check_auth + return render_forbidden unless observed_logged_user? + end + + def find_applied_transfer_project + @applied_transfer_project = AppliedTransferProject.find_by_id params[:id] + end + + def find_project + @project = @applied_transfer_project.project + end +end \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 07e1c72f..d163547e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -2,8 +2,8 @@ class UsersController < ApplicationController include ApplicationHelper include Ci::DbConnectable - before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users] - before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users] + before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard] + before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard] before_action :require_login, only: %i[me list change_password change_email] before_action :connect_to_ci_db, only: [:get_user_info] skip_before_action :check_sign, only: [:attachment_show] @@ -27,12 +27,24 @@ class UsersController < ApplicationController def show #待办事项,现在未做 - @undo_events = 0 + if User.current.login == @user.login + @waiting_applied_messages = @user.applied_messages.waiting + @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common + @undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + else + @waiting_applied_messages = AppliedMessage.none + @common_applied_transfer_projects = AppliedTransferProject.none + @undo_events = 0 + end #用户的组织数量 # @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 + user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common") + @user_org_count = user_organizations.size + normal_projects = Project.members_projects(@user.id).to_sql + org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql + projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct + user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible @projects_common_count = user_projects.common.size @projects_mirrior_count = user_projects.mirror.size @projects_sync_mirrior_count = user_projects.sync_mirror.size @@ -56,6 +68,9 @@ class UsersController < ApplicationController @watchers = paginate(watchers) end + def hovercard + end + def update @user = User.find params[:id] @user.update!(user_params) diff --git a/app/controllers/version_releases_controller.rb b/app/controllers/version_releases_controller.rb index 1b56236a..ac17c7d4 100644 --- a/app/controllers/version_releases_controller.rb +++ b/app/controllers/version_releases_controller.rb @@ -8,7 +8,7 @@ class VersionReleasesController < ApplicationController version_releases = Gitea::Versions::ListService.new(@user.gitea_token, @user.try(:login), @repository.try(:identifier)).call @version_releases = version_releases @user_permission = current_user.present? && (current_user == @user || current_user.admin?) - @forge_releases = @repository.version_releases.select(:id,:version_gid).includes(:attachments) + @forge_releases = @repository.version_releases.select(:id,:version_gid, :created_at).includes(:attachments) end def new diff --git a/app/docs/slate/source/includes/_projects.md b/app/docs/slate/source/includes/_projects.md index 0a46d8cc..87a37c45 100644 --- a/app/docs/slate/source/includes/_projects.md +++ b/app/docs/slate/source/includes/_projects.md @@ -526,3 +526,241 @@ await octokit.request('POST /api/jaser/jasder_test/forks.json') "identifier": "newadm" } ``` + +## 用户管理的组织列表 +用户管理的组织列表 + +> 示例: + +```shell +curl -X GET \ +http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizations.json | jq +``` + +```javascript +await octokit.request('GET /api/:owner/:repo/applied_transfer_projects/organizations') +``` + +### HTTP 请求 +`GET api/:owner/:repo/applied_transfer_projects/organizations` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +owner |是| |string |用户登录名 +repo |是| |string |项目标识identifier + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +name |string|组织标识 +nickname |string|组织名称 +description|string|组织描述 +avatar_url|string|组织头像 + + +> 返回的JSON示例: + +```json +{ + "total_count": 3, + "organizations": [ + { + "id": 9, + "name": "ceshi_org", + "nickname": "测试组织", + "description": "测试组织", + "avatar_url": "images/avatars/Organization/9?t=1612706073" + }, + { + "id": 51, + "name": "ceshi", + "nickname": "测试组织哈哈哈", + "description": "23212312", + "avatar_url": "images/avatars/Organization/51?t=1618800723" + }, + { + "id": 52, + "name": "ceshi1", + "nickname": "身份卡手动阀", + "description": "1231手动阀是的", + "avatar_url": "images/avatars/Organization/52?t=1618805056" + } + ] +} +``` + +## 迁移项目 +迁移项目,edit接口is_transfering为true表示正在迁移 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects.json +``` + +```javascript +await octokit.request('POST /api/:owner/:repo/applied_transfer_projects.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/applied_transfer_projects.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | +|owner_name|是| |string |迁移对象标识 | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |项目id | +|status |string |项目迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |项目迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | +> 返回的JSON示例: + +```json +{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "1分钟前" +} +``` + +## 取消迁移项目 +迁移项目,edit接口is_transfering为true表示正在迁移 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/cancel.json +``` + +```javascript +await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/applied_transfer_projects/cancel.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |迁移id | +|status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | +> 返回的JSON示例: + +```json +{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "1分钟前" +} +``` \ No newline at end of file diff --git a/app/docs/slate/source/includes/_users.md b/app/docs/slate/source/includes/_users.md index 62a6391e..1d1afc02 100644 --- a/app/docs/slate/source/includes/_users.md +++ b/app/docs/slate/source/includes/_users.md @@ -1,3 +1,9 @@ + # Users ## 获取当前登陆用户信息 @@ -40,3 +46,390 @@ await octokit.request('GET /api/users/me.json') + +## 待办事项-用户通知信息 +待办事项-用户通知信息 + +> 示例: + +```shell +curl -X GET http://localhost:3000/api/users/yystopf/applied_messages.json +``` + +```javascript +await octokit.request('GET /api/users/:login/applied_messages.json') +``` + +### HTTP 请求 +`GET /api/users/:login/applied_messages.json` + +### 请求字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|login |string |用户标识 | + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|applied |object |通知主体 | +|applied.id |int |通知主体的迁移id | +|applied.status |string |通知主体的迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|applied.time_ago |string |通知主体的迁移创建的时间 | +|applied.project.id |int |通知主体的迁移项目的id | +|applied.project.identifier |string |通知主体的迁移项目的标识 | +|applied.project.name |string |通知主体的迁移项目的名称 | +|applied.project.description |string |通知主体的迁移项目的描述 | +|applied.project.is_public |bool |通知主体的迁移项目是否公开 | +|applied.project.owner.id |bool |通知主体的迁移项目拥有者id | +|applied.project.owner.type |string |通知主体的迁移项目拥有者类型 | +|applied.project.owner.name |string |通知主体的迁移项目拥有者昵称 | +|applied.project.owner.login |string |通知主体的迁移项目拥有者标识 | +|applied.project.owner.image_url |string |通知主体的迁移项目拥有者头像 | +|applied.user.id |int |通知主体的迁移创建者的id | +|applied.user.type |string |通知主体的迁移创建者的类型 | +|applied.user.name |string |通知主体的迁移创建者的名称 | +|applied.user.login |string |通知主体的迁移创建者的标识 | +|applied.user.image_url |string |通知主体的迁移创建者头像 | +|applied.owner.id |int |通知主体的迁移接受者的id | +|applied.owner.type |string |通知主体的迁移接受者的类型 | +|applied.owner.name |string |通知主体的迁移接受者的名称 | +|applied.owner.login |string |通知主体的迁移接受者的标识 | +|applied.owner.image_url |string |通知主体的迁移接受者头像 | +|applied_type |string |通知类型 | +|name |string | 通知内容 | +|viewed |string|是否已读,waiting:未读,viewed:已读| +|status |string|通知状态, canceled:已取消,common: 正常,successed:成功,failure:失败| +|time_ago |string|通知时间| + + +> 返回的JSON示例: + +```json +{ + "total_count": 5, + "applied_messages": [ + { + "applied": { + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "35分钟前" + }, + "applied_user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "applied_type": "AppliedTransferProject", + "name": "正在将【测试项目啊1】仓库转移给【测试组织】", + "viewed": "viewed", + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "35分钟前" + }, + ... + ] +} +``` + +## 待办事项-接受仓库 +待办事项-接受仓库 + +> 示例: + +```shell +curl -X GET http://localhost:3000/api/users/yystopf/applied_transfer_projects.json +``` + +```javascript +await octokit.request('GET /api/users/:login/applied_transfer_projects.json') +``` + +### HTTP 请求 +`GET /api/users/:login/applied_transfer_projects.json` + +### 请求字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|login |string |用户标识 | + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |迁移id | +|status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | + + +> 返回的JSON示例: + +```json +{ + "total_count": 4, + "applied_transfer_projects": [ + { + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + }, + "id": 1, + "status": "canceled", + "created_at": "2021-04-25 18:06", + "time_ago": "16小时前" + }, + ... + ] +} +``` + +## 用户接受迁移 +用户接受迁移 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/accept.json +``` + +```javascript +await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/accept.json') +``` + +### HTTP 请求 +`GET /api/users/:login/applied_transfer_projects/:id/accept.json` + +### 请求字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|login |string |用户标识 | +|id |int |迁移id | + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |迁移id | +|status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | + + +> 返回的JSON示例: + +```json +{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + }, + "id": 1, + "status": "canceled", + "created_at": "2021-04-25 18:06", + "time_ago": "16小时前" +} +``` + +## 用户拒绝迁移 +用户拒绝迁移 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/refuse.json +``` + +```javascript +await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/refuse.json') +``` + +### HTTP 请求 +`GET /api/users/:login/applied_transfer_projects/:id/refuse.json` + +### 请求字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|login |string |用户标识 | +|id |int |迁移id | + +### 返回字段说明: +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |迁移id | +|status |string |迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝| +|time_ago |string |迁移创建的时间 | +|project.id |int |迁移项目的id | +|project.identifier |string |迁移项目的标识 | +|project.name |string |迁移项目的名称 | +|project.description |string |迁移项目的描述 | +|project.is_public |bool |迁移项目是否公开 | +|project.owner.id |bool |迁移项目拥有者id | +|project.owner.type |string |迁移项目拥有者类型 | +|project.owner.name |string |迁移项目拥有者昵称 | +|project.owner.login |string |迁移项目拥有者标识 | +|project.owner.image_url |string |迁移项目拥有者头像 | +|user.id |int |迁移创建者的id | +|user.type |string |迁移创建者的类型 | +|user.name |string |迁移创建者的名称 | +|user.login |string |迁移创建者的标识 | +|user.image_url |string |迁移创建者头像 | +|owner.id |int |迁移接受者的id | +|owner.type |string |迁移接受者的类型 | +|owner.name |string |迁移接受者的名称 | +|owner.login |string |迁移接受者的标识 | +|owner.image_url |string |迁移接受者头像 | + + +> 返回的JSON示例: + +```json +{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + }, + "id": 1, + "status": "canceled", + "created_at": "2021-04-25 18:06", + "time_ago": "16小时前" +} +``` \ No newline at end of file diff --git a/app/forms/issues/create_form.rb b/app/forms/issues/create_form.rb new file mode 100644 index 00000000..7dde8ecd --- /dev/null +++ b/app/forms/issues/create_form.rb @@ -0,0 +1,11 @@ +class Issues::CreateForm + include ActiveModel::Model + + attr_accessor :subject + + validates :subject, presence: { message: "不能为空" } + + validates :subject, length: { maximum: 80, too_long: "不能超过80个字符" } + + +end diff --git a/app/forms/issues/update_form.rb b/app/forms/issues/update_form.rb new file mode 100644 index 00000000..7447c8cc --- /dev/null +++ b/app/forms/issues/update_form.rb @@ -0,0 +1,10 @@ +class Issues::UpdateForm + include ActiveModel::Model + + attr_accessor :subject + + validates :subject, presence: { message: "不能为空" } + + validates :subject, length: { maximum: 80, too_long: "不能超过80个字符" } + +end \ No newline at end of file diff --git a/app/forms/organizations/create_form.rb b/app/forms/organizations/create_form.rb new file mode 100644 index 00000000..d92a9904 --- /dev/null +++ b/app/forms/organizations/create_form.rb @@ -0,0 +1,11 @@ +class Organizations::CreateForm < BaseForm + NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 + attr_accessor :name, :description, :website, :location, :repo_admin_change_team_access, :visibility, :max_repo_creation, :nickname + + validates :name, :nickname, :visibility, presence: true + validates :name, :nickname, length: { maximum: 100 } + validates :location, length: { maximum: 50 } + validates :description, length: { maximum: 200 } + validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } + +end diff --git a/app/forms/organizations/create_team_form.rb b/app/forms/organizations/create_team_form.rb new file mode 100644 index 00000000..6d816b0e --- /dev/null +++ b/app/forms/organizations/create_team_form.rb @@ -0,0 +1,10 @@ +class Organizations::CreateTeamForm < BaseForm + NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 + attr_accessor :name, :nickname, :description, :authorize, :includes_all_project, :can_create_org_project, :unit_types + + validates :name, :nickname, presence: true + validates :name, :nickname, length: { maximum: 100 } + validates :description, length: { maximum: 200 } + validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } + +end diff --git a/app/forms/projects/create_form.rb b/app/forms/projects/create_form.rb index 2838996b..0b57f215 100644 --- a/app/forms/projects/create_form.rb +++ b/app/forms/projects/create_form.rb @@ -2,11 +2,15 @@ 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, :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: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } + validates :name, length: { maximum: 50 } + validates :repository_name, length: { maximum: 100 } + validates :description, length: { maximum: 200 } + validate :check_ignore, :check_license, :check_owner, :check_max_repo_creation validate do check_project_category(project_category_id) diff --git a/app/forms/projects/update_form.rb b/app/forms/projects/update_form.rb index 4696beaa..65810a82 100644 --- a/app/forms/projects/update_form.rb +++ b/app/forms/projects/update_form.rb @@ -1,4 +1,11 @@ class Projects::UpdateForm < BaseForm - attr_reader :name, :description, :repository_name, :project_category_id + attr_accessor :name, :description, :project_category_id, :project_language_id, :private + validates :name, :description, :project_category_id, :project_language_id, presence: true + validates :name, length: { maximum: 50 } + validates :description, length: { maximum: 200 } + validate do + check_project_category(project_category_id) + check_project_language(project_language_id) + end end diff --git a/app/forms/users/login_form.rb b/app/forms/users/login_form.rb new file mode 100644 index 00000000..2634a36c --- /dev/null +++ b/app/forms/users/login_form.rb @@ -0,0 +1,8 @@ +class Users::LoginForm + include ActiveModel::Model + + attr_accessor :password, :login + + validates :login, presence: true + validates :password, presence: true, length: { minimum: 8, maximum: 16 }, format: { with: CustomRegexp::PASSWORD, message: "8~16位,支持字母数字和符号" } +end \ No newline at end of file diff --git a/app/helpers/admins/faqs_helper.rb b/app/helpers/admins/faqs_helper.rb new file mode 100644 index 00000000..0316d44f --- /dev/null +++ b/app/helpers/admins/faqs_helper.rb @@ -0,0 +1,2 @@ +module Admins::FaqsHelper +end diff --git a/app/helpers/admins/projects_helper.rb b/app/helpers/admins/projects_helper.rb new file mode 100644 index 00000000..c6d94c4c --- /dev/null +++ b/app/helpers/admins/projects_helper.rb @@ -0,0 +1,14 @@ +module Admins::ProjectsHelper + + def link_to_project(project) + owner = project.owner + + if owner.is_a?(User) + link_to(project.owner&.real_name, "/users/#{project&.owner&.login}", target: '_blank') + elsif owner.is_a?(Organization) + link_to(project.owner&.real_name, "/organize/#{project&.owner&.login}", target: '_blank') + else + "" + end + end +end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 30953bf5..ccc45df0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -35,12 +35,6 @@ module ApplicationHelper course.course_modules.find_by(module_type: "graduation").try(:id) end - # 是否关注 - # from_user_id为被关注的用户 - def follow?(from_user_id, user_id) - Watcher.where(watchable_type: 'Principal', watchable_id: from_user_id, user_id: user_id).exists? - end - # git用户 # git用户命名规则:login+"@educoder.net" def git_username(email) @@ -144,17 +138,12 @@ module ApplicationHelper if File.exist?(disk_filename(source&.class, source&.id)) ctime = File.ctime(disk_filename(source.class, source.id)).to_i if %w(User Organization).include?(source.class.to_s) - File.join(relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" + File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" else File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" end elsif source.class.to_s == 'User' - str = source.user_extension.try(:gender).to_i == 0 ? "b" : "g" - File.join(relative_path, "#{source.class}", str) - elsif source.class.to_s == 'Subject' - File.join("images","educoder", "index", "subject", "subject#{rand(17)}.jpg") - elsif source.class.to_s == 'Shixun' - File.join("images","educoder", "index", "shixun", "shixun#{rand(23)}.jpg") + source.get_letter_avatar_url end end diff --git a/app/helpers/helps/faqs_helper.rb b/app/helpers/helps/faqs_helper.rb new file mode 100644 index 00000000..68b8279f --- /dev/null +++ b/app/helpers/helps/faqs_helper.rb @@ -0,0 +1,2 @@ +module Helps::FaqsHelper +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b212b50e..226096c2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -34,15 +34,14 @@ module ProjectsHelper end def json_response(project, user) - # repo = project.repository - repo = Repository.includes(:mirror).select(:id, :mirror_url).find_by(project: project) + repo = Repository.includes(:mirror).select(:id, :mirror_url, :source_clone_url).find_by(project: project) tmp_json = {} unless project.common? tmp_json = tmp_json.merge({ mirror_status: repo.mirror_status, mirror_num: repo.mirror_num, - mirror_url: repo.mirror_url, + mirror_url: repo.remote_mirror_url, first_sync: repo.first_sync? }) end @@ -90,8 +89,4 @@ module ProjectsHelper def render_educoder_avatar_url(project_educoder) [Rails.application.config_for(:configuration)['educoder']['cdn_url'], project_educoder&.image_url].join('/') end - - def render_avatar_url(owner) - ['images', url_to_avatar(owner)].join('/') - end end diff --git a/app/helpers/tag_chosen_helper.rb b/app/helpers/tag_chosen_helper.rb index afd51170..1e7879d1 100644 --- a/app/helpers/tag_chosen_helper.rb +++ b/app/helpers/tag_chosen_helper.rb @@ -124,14 +124,13 @@ module TagChosenHelper end def render_cache_collaborators(project) - cache_key = "all_collaborators/#{project.members.maximum('created_on')}" - + cache_key = "all_collaborators/#{project.all_collaborators.maximum('created_on')}" Rails.cache.fetch(cache_key) do - project.members.includes(:user).collect do |event| + project.all_collaborators.order(created_on: :desc).collect do |user| { - id: event.user&.id, - name: event.user&.show_real_name, - avatar_url: url_to_avatar(event.user), + id: user&.id, + name: user&.show_real_name, + avatar_url: url_to_avatar(user), is_chosen: '0' } end @@ -171,10 +170,8 @@ module TagChosenHelper # depended_issues_id = @depended_issues_id end - project_members = project.members_user_infos project_members_info = [] #指派给 - project_members.includes(user: :user_extension).each do |member| - user = member&.user + project.all_collaborators.includes(:user_extension).each do |user| if user real_name = user.try(:show_real_name) user_id = user.id diff --git a/app/jobs/send_transfer_project_applied_message_job.rb b/app/jobs/send_transfer_project_applied_message_job.rb new file mode 100644 index 00000000..92ea306d --- /dev/null +++ b/app/jobs/send_transfer_project_applied_message_job.rb @@ -0,0 +1,47 @@ +class SendTransferProjectAppliedMessageJob < ApplicationJob + queue_as :default + + def perform(applied_transfer_project, applied_user, message_status) + project = applied_transfer_project.project + owner = project.owner + return unless project.present? + return unless owner.present? + if owner.is_a?(Organization) + receivers = project.managers + owner.team_users.joins(:team).where(teams: {authorize: %w(owner admin)}) + else + receivers = project.managers + end + receivers.each do |rec| + next if applied_user.id == rec.user_id # 自己不要给自己发通知 + AppliedMessage.create!(user_id: rec.user_id, + applied: applied_transfer_project, + status: message_status, + name: build_name(project.name, applied_transfer_project&.owner&.real_name, message_status, applied_user&.real_name), + applied_user_id: applied_user.id, + project_id: project.id) + end + if message_status == 'successed' # 如果转移成功,给转移发起者发通知已转移成功 + AppliedMessage.find_or_create_by!(user_id: applied_transfer_project.user_id, + applied: applied_transfer_project, + status: message_status, + name: build_name(project.name, applied_transfer_project&.owner&.real_name, message_status), + applied_user_id: applied_user.id, + project_id: project.id) + end + end + + private + def build_name(repo_name, owner_name, message_status, applied_name="") + case message_status + when 'canceled' + return "取消转移【#{repo_name}】仓库" + when 'common' + return "正在将【#{repo_name}】仓库转移给【#{owner_name}】" + when 'successed' + return "【#{repo_name}】仓库成功转移给【#{owner_name}】" + when 'failure' + return "拒绝转移【#{repo_name}】仓库" + end + "" + end +end \ No newline at end of file diff --git a/app/models/applied_message.rb b/app/models/applied_message.rb index 8098e6e0..b3ebad34 100644 --- a/app/models/applied_message.rb +++ b/app/models/applied_message.rb @@ -19,5 +19,10 @@ class AppliedMessage < ApplicationRecord belongs_to :user belongs_to :applied, polymorphic: true + belongs_to :project + belongs_to :applied_user, class_name: 'User' + + enum viewed: {waiting: 0, viewed: 1} + enum status: {canceled: -1, common: 0, successed: 1, failure: 2} # -1 已取消 0 正在操作 1 操作成功 2 操作失败 end diff --git a/app/models/applied_transfer_project.rb b/app/models/applied_transfer_project.rb new file mode 100644 index 00000000..d0e0e5c4 --- /dev/null +++ b/app/models/applied_transfer_project.rb @@ -0,0 +1,28 @@ +# == Schema Information +# +# Table name: applied_transfer_projects +# +# id :integer not null, primary key +# project_id :integer +# owner_id :integer +# user_id :integer +# status :integer default("0") +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_applied_transfer_projects_on_owner_id (owner_id) +# index_applied_transfer_projects_on_project_id (project_id) +# index_applied_transfer_projects_on_user_id (user_id) +# + +class AppliedTransferProject < ApplicationRecord + belongs_to :project + belongs_to :user # 操作者 + belongs_to :owner # 接收个人或组织 + + has_many :applied_messages, as: :applied, dependent: :destroy + + enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝 +end diff --git a/app/models/ci/user.rb b/app/models/ci/user.rb index fc82596c..e4a4d062 100644 --- a/app/models/ci/user.rb +++ b/app/models/ci/user.rb @@ -47,6 +47,7 @@ # watchers_count :integer default("0") # devops_step :integer default("0") # gitea_token :string(255) +# platform :string(255) # # Indexes # diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/models/concerns/project_operable.rb b/app/models/concerns/project_operable.rb index e151613d..b8adf5c8 100644 --- a/app/models/concerns/project_operable.rb +++ b/app/models/concerns/project_operable.rb @@ -11,6 +11,14 @@ module ProjectOperable has_many :team_projects, dependent: :destroy end + def set_owner_permission(creator) + return unless owner.is_a?(Organization) + owner.build_permit_team_projects!(id) + # 避免自己创建的项目,却无法拥有访问权,因为该用户所在团队暂未获得项目访问权 + return if creator.nil? || owner.is_owner?(creator.id) + add_member!(creator.id, "Manager") + end + def add_member!(user_id, role_name='Developer') member = members.create!(user_id: user_id) set_developer_role(member, role_name) @@ -78,12 +86,16 @@ module ProjectOperable 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) + reporters.exists?(user_id: user.id) || owner.is_only_read?(user.id) else false end end + def operator?(user) + user.admin? || !reporter?(user) + end + def set_developer_role(member, role_name) role = Role.find_by(name: role_name) member.member_roles.create!(role: role) @@ -92,4 +104,22 @@ module ProjectOperable def has_menu_permission(unit_type) self.project_units.where(unit_type: unit_type).exists? end + + def all_collaborators + member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager Developer Reporter)}).to_sql + team_user_sql = User.joins(teams: :team_projects).where(team_projects: {project_id: self.id}).to_sql + return User.from("( #{ member_sql } UNION #{ team_user_sql } ) AS users").distinct + end + + def all_developers + member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager Developer)}).to_sql + team_user_sql = User.joins(teams: :team_projects).where(teams: {authorize: %w(owner admin write)}, team_projects: {project_id: self.id}).to_sql + return User.from("( #{ member_sql } UNION #{ team_user_sql } ) AS users").distinct + end + + def all_managers + member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager)}).to_sql + team_user_sql = User.joins(teams: :team_projects).where(teams: {authorize: %w(owner admin)},team_projects: {project_id: self.id}).to_sql + return User.from("( #{ member_sql} UNION #{ team_user_sql } ) AS users").distinct + end end diff --git a/app/models/concerns/watchable.rb b/app/models/concerns/watchable.rb index 4c52cf99..008d382f 100644 --- a/app/models/concerns/watchable.rb +++ b/app/models/concerns/watchable.rb @@ -6,6 +6,7 @@ module Watchable has_many :watcher_users, through: :watchers, source: :user, validate: false scope :watched_by, -> (user_id) { includes(:watchers).where(watchers: { user_id: user_id }) } + scope :following, -> (user_id) { watched_by(user_id) } end def watched?(watchable) @@ -21,6 +22,24 @@ module Watchable obj.destroy! if obj.present? end + # 我正在关注的、我追随的 + def following + User.following(self.id) + end + + def following_count + following.size + end + + # 关注我的、我的粉丝、我的追随者 + def followers + watcher_users + end + + def followers_count + followers.size + end + module ClassMethods end end diff --git a/app/models/faq.rb b/app/models/faq.rb new file mode 100644 index 00000000..5965de03 --- /dev/null +++ b/app/models/faq.rb @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: faqs +# +# id :integer not null, primary key +# question :string(255) +# url :string(255) +# created_at :datetime not null +# updated_at :datetime not null +# + +class Faq < ApplicationRecord + + validates :question, presence: true,length: { maximum: 100, too_long: "最多%{count}个字符" } + validates :url, format: { with: CustomRegexp::URL, multiline: true, message: "格式不正确" } + + scope :select_without_id, -> { select(:question, :url) } + scope :search_question, ->(keyword) { where("question LIKE ?", "%#{keyword}%") unless keyword.blank?} + +end diff --git a/app/models/organization.rb b/app/models/organization.rb index fbf3def7..444938e7 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -47,6 +47,7 @@ # watchers_count :integer default("0") # devops_step :integer default("0") # gitea_token :string(255) +# platform :string(255) # # Indexes # @@ -108,12 +109,23 @@ class Organization < Owner team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).present? end + def is_only_read?(user_id) + team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read)}).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 build_permit_team_projects!(project_id) + teams.where(includes_all_project: true).each do |team| + TeamProject.build(id, team.id, project_id) + end + end + def real_name name = lastname + firstname name = name.blank? ? (nickname.blank? ? login : nickname) : name diff --git a/app/models/owner.rb b/app/models/owner.rb index 1d537a1e..b65ee054 100644 --- a/app/models/owner.rb +++ b/app/models/owner.rb @@ -66,4 +66,6 @@ class Owner < ApplicationRecord has_many :projects, foreign_key: :user_id, dependent: :destroy has_many :repositories, foreign_key: :user_id, dependent: :destroy + has_many :applied_transfer_projects, dependent: :destroy + end diff --git a/app/models/project.rb b/app/models/project.rb index 7b1b9159..a75cf3de 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,78 +1,76 @@ -# == 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") -# default_branch :string(255) default("master") -# website :string(255) -# -# 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 +# 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") +# license_id :integer +# ignore_id :integer +# default_branch :string(255) default("master") +# website :string(255) +# lesson_url :string(255) +# +# 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 @@ -116,6 +114,7 @@ class Project < ApplicationRecord has_one :project_detail, dependent: :destroy has_many :team_projects, dependent: :destroy has_many :project_units, dependent: :destroy + has_one :applied_transfer_project,-> { order created_at: :desc }, 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)} @@ -162,6 +161,7 @@ class Project < ApplicationRecord #创建项目管理员 def check_project_members + return if owner.is_a?(Organization) unless members.present? && members.exists?(user_id: self.user_id) member_params = { user_id: self.user_id, @@ -298,4 +298,7 @@ class Project < ApplicationRecord update_column(:updated_on, time) end + def is_transfering + applied_transfer_project&.common? ? true : false + end end diff --git a/app/models/project_unit.rb b/app/models/project_unit.rb index 0f427534..a07f62b4 100644 --- a/app/models/project_unit.rb +++ b/app/models/project_unit.rb @@ -20,13 +20,18 @@ class ProjectUnit < ApplicationRecord validates :unit_type, uniqueness: { scope: :project_id} - def self.init_types(project_id) - ProjectUnit::unit_types.each do |_, v| + def self.init_types(project_id, project_type='common') + unit_types = project_type == 'sync_mirror' ? ProjectUnit::unit_types.except("pulls") : ProjectUnit::unit_types + unit_types.each do |_, v| self.create!(project_id: project_id, unit_type: v) end end def self.update_by_unit_types!(project, types) + # 同步镜像项目不能有合并请求模块 + types.delete("pulls") if project.sync_mirror? + # 默认code类型自动创建 + types << "code" project.project_units.where.not(unit_type: types).each(&:destroy!) types.each do |type| project.project_units.find_or_create_by!(unit_type: type) diff --git a/app/models/repository.rb b/app/models/repository.rb index 978bc3c5..0daa4b91 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -22,6 +22,8 @@ # version_releases_count :integer default("0") # fork_url :string(255) # is_mirror :boolean default("0") +# accelerator_url :string(255) default("") +# source_clone_url :string(255) default("") # # Indexes # @@ -76,4 +78,9 @@ class Repository < ApplicationRecord end end + def remote_mirror_url + source_clone_url.blank? ? mirror_url : source_clone_url + end + + end diff --git a/app/models/team.rb b/app/models/team.rb index 82a3e88c..c2596390 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -14,6 +14,7 @@ # gtid :integer # created_at :datetime not null # updated_at :datetime not null +# nickname :string(255) # # Indexes # @@ -32,9 +33,10 @@ class Team < ApplicationRecord 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) + def self.build(organization_id, name, nickname, description, authorize, includes_all_project, can_create_org_project) self.create!(organization_id: organization_id, name: name, + nickname: nickname, description: description, authorize: authorize, includes_all_project: includes_all_project, diff --git a/app/models/user.rb b/app/models/user.rb index 97d63280..948a55f3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,6 +47,7 @@ # watchers_count :integer default("0") # devops_step :integer default("0") # gitea_token :string(255) +# platform :string(255) # # Indexes # @@ -68,6 +69,7 @@ class User < Owner include Likeable include BaseModel include Droneable + include User::Avatar # include Searchable::Dependents::User # devops step @@ -136,10 +138,6 @@ class User < Owner 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_one :ci_cloud_account, class_name: 'Ci::CloudAccount', dependent: :destroy # 认证 @@ -150,9 +148,14 @@ class User < Owner has_many :trail_auth_apply_actions, -> { where(container_type: 'TrialAuthorization') }, class_name: 'ApplyAction' # has_many :attendances - + has_many :applied_messages, dependent: :destroy + has_many :operate_applied_messages, class_name: 'AppliedMessage', dependent: :destroy # 项目 has_many :applied_projects, dependent: :destroy + has_many :operate_applied_transfer_projects, class_name: 'AppliedTransferProject', dependent: :destroy + has_many :members, dependent: :destroy + has_many :team_users, dependent: :destroy + has_many :teams, through: :team_users # 教学案例 # has_many :libraries, dependent: :destroy @@ -165,7 +168,8 @@ class User < Owner # 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? + sql = "CONCAT(lastname, firstname) LIKE :keyword OR login LIKE :keyword OR mail LIKE :keyword OR nickname LIKE :keyword OR phone LIKE :keyword" + where(sql, :search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? } scope :simple_select, -> {select(:id, :login, :lastname,:firstname, :nickname, :gitea_uid, :type)} @@ -191,6 +195,10 @@ class User < Owner validate :validate_sensitive_string validate :validate_password_length + def name + login + end + # 删除自动登录的token,一旦退出下次会提示需要登录 def delete_autologin_token(value) Token.where(:user_id => id, :action => 'autologin', :value => value).delete_all @@ -205,7 +213,7 @@ class User < Owner end def project_manager?(project) - project.managers.exists?(user: self) || self.admin? + project.manager?(self) || self.admin? end # 学号 diff --git a/app/models/user/avatar.rb b/app/models/user/avatar.rb new file mode 100644 index 00000000..22f770cd --- /dev/null +++ b/app/models/user/avatar.rb @@ -0,0 +1,43 @@ +require 'letter_avatar/has_avatar' +require 'chinese_pinyin' + +class User + module Avatar + extend ActiveSupport::Concern + include LetterAvatar::HasAvatar + + def username + self.lastname.blank? ? self.login : Pinyin.t(self.lastname) + end + + def get_letter_avatar_url(size = :lg) + avatar_path(self.username, size).split('public/')&.last + end + + def self.get_letter_avatar_url(name) + return "" if name.blank? + avatar = LetterAvatar.generate Pinyin.t(name), 120 + avatar.split('public/')&.last + end + + def avatar_path(username, size) + LetterAvatar.generate username, avatar_size(size) + end + + # 返回头像尺寸 + # xs: 22px + # sm: 32px + # md: 48px + # lg: 120px + def avatar_size(size) + case size + when :xs then 22 + when :sm then 32 + when :md then 48 + when :lg then 120 + else size + end + end + + end +end diff --git a/app/queries/projects/list_my_query.rb b/app/queries/projects/list_my_query.rb index 8ad0d0b0..5bda5612 100644 --- a/app/queries/projects/list_my_query.rb +++ b/app/queries/projects/list_my_query.rb @@ -18,7 +18,9 @@ class Projects::ListMyQuery < ApplicationQuery end if params[:category].blank? - projects = projects.members_projects(user.id) + normal_projects = projects.members_projects(user.id).to_sql + org_projects = projects.joins(team_projects: [team: :team_users]).where(team_users: {user_id: user.id}).to_sql + projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct elsif params[:category].to_s == "join" normal_projects = projects.where.not(user_id: user.id).members_projects(user.id).to_sql org_projects = projects.joins(team_projects: [team: :team_users]).where(team_users: {user_id: user.id}).to_sql diff --git a/app/services/gitea/accelerator/migrate_service.rb b/app/services/gitea/accelerator/migrate_service.rb new file mode 100644 index 00000000..015a09b1 --- /dev/null +++ b/app/services/gitea/accelerator/migrate_service.rb @@ -0,0 +1,148 @@ +class Gitea::Accelerator::MigrateService < ApplicationService + attr_reader :params + + # params description: + # { + # auth_username string + # clone_addr* string #clone地址 + # description string + # issues boolean + # labels boolean + # milestones boolean + # mirror boolean + # private boolean + # pull_requests boolean + # releases boolean + # repo_name* string #仓库名称 + # uid* integer($int64) #gitea用户id或组织id + # wiki boolean + # } + # EX: + # params = { + # clone_addr: 'xxx.com', + # repo_name: 'repo_name', + # uid: 2, + # private: false + # } + + def initialize(params) + @params = params + end + + def call + return error('[gitea:] accelerator config missing') if check_accelerator! + response = post(url, request_params) + + render_status(response) + end + + private + + def request_params + { + uid: access_uid, + clone_addr: params[:clone_addr], + repo_name: params[:repository_name], + auth_username: params[:auth_username], + auth_password: params[:auth_password], + mirror: ActiveModel::Type::Boolean.new.cast(params[:is_mirror]) + } + end + + def url + "/repos/migrate".freeze + end + + def post(url, params) + puts "[gitea] request params: #{params}" + puts "[gitea] access_username: #{access_username}" + puts "[gitea] access_password: #{access_password}" + conn.post do |req| + req.url full_url(url) + req.body = params.to_json + end + end + + def conn + @client ||= begin + Faraday.new(url: domain) do |req| + req.request :url_encoded + req.headers['Content-Type'] = 'application/json' + req.response :logger # 显示日志 + req.adapter Faraday.default_adapter + req.basic_auth(access_username, access_password) + end + end + @client + end + + def base_url + accelerator["base_url"] + end + + def domain + accelerator["domain"] + end + + def api_url + [domain, base_url].join('') + end + + def full_url(api_rest, action='post') + url = [api_url, api_rest].join('').freeze + url = action === 'get' ? url : URI.escape(url) + puts "[gitea] request url: #{url}" + url + end + + def access_username + accelerator["access_key_id"] + end + + def access_password + accelerator["access_key_secret"] + end + + def access_uid + accelerator["access_admin_uid"] + end + + def accelerator + Gitea.gitea_config[:accelerator] + end + + def render_status(response) + puts "[gitea] response status: #{response.status}" + puts "[gitea] response body: #{response.body}" + case response.status + when 201 + success + when 403 + error('APIForbiddenError') + when 422 + error('APIValidationError') + else + error("MigrateError") + end + end + + def error(message) + { + status: :error, + message: message, + data: nil + } + end + + def success(data=nil) + { + status: :success, + message: nil, + data: data + } + end + + def check_accelerator! + accelerator.blank? || access_username.blank? || access_password.blank? || domain.blank? + end +end \ No newline at end of file diff --git a/app/services/gitea/client_service.rb b/app/services/gitea/client_service.rb index 1c13bc42..0ef04199 100644 --- a/app/services/gitea/client_service.rb +++ b/app/services/gitea/client_service.rb @@ -176,6 +176,25 @@ class Gitea::ClientService < ApplicationService [status, message, body] end + def render_gitea_response(response) + status = response.status + body = response&.body + + log_error(status, body) + message = nil + begin + translate = YAML.load(File.read('config/gitea_response.yml')) + + self.class.to_s.underscore.split("/").map{|i| translate=translate[i]} + message = body.nil? ? translate[status]['default'] : JSON.parse(body)['message'] + message = translate[status][message].nil? ? message : translate[status][message] + + return [status, message] + rescue + return [status, message] + end + end + def get_body_by_status(status, body) body, message = case status diff --git a/app/services/gitea/pull_request/get_service.rb b/app/services/gitea/pull_request/get_service.rb index 6a7ec30f..601c669e 100644 --- a/app/services/gitea/pull_request/get_service.rb +++ b/app/services/gitea/pull_request/get_service.rb @@ -1,15 +1,14 @@ # Get a pull request class Gitea::PullRequest::GetService < Gitea::ClientService - attr_reader :user, :repo, :pull_request_id + attr_reader :owner, :repo, :number, :token - # user: 用户 - # repo: 仓库名称/标识 - # pull_request_id: pull request主键id - def initialize(user, repo, pull_request_id) - super({token: user.gitea_token}) - @user = user + #eq: + # Gitea::PullRequest::GetService.call(user.login, repository.identifier, pull.gpid, user.gitea_token) + def initialize(owner, repo, number, token=nil) + @owner = owner @repo = repo - @pull_request_id = pull_request_id + @number = number + @token = token end def call @@ -19,11 +18,11 @@ class Gitea::PullRequest::GetService < Gitea::ClientService private def params - Hash.new.merge(token: user.gitea_token) + Hash.new.merge(token: token) end def url - "/repos/#{user.login}/#{repo}/pulls/#{pull_request_id}".freeze + "/repos/#{owner}/#{repo}/pulls/#{number}".freeze end def render_result(response) @@ -31,7 +30,7 @@ class Gitea::PullRequest::GetService < Gitea::ClientService when 200 JSON.parse(response.body) else - nil + {} end end end diff --git a/app/services/gitea/pull_request/merge_service.rb b/app/services/gitea/pull_request/merge_service.rb index c33c8781..bdf2b23b 100644 --- a/app/services/gitea/pull_request/merge_service.rb +++ b/app/services/gitea/pull_request/merge_service.rb @@ -20,7 +20,7 @@ class Gitea::PullRequest::MergeService < Gitea::ClientService def call response = post(url, request_params) - render_200_no_body(response) + render_gitea_response(response) end private diff --git a/app/services/issues/list_query_service.rb b/app/services/issues/list_query_service.rb index dbb502ab..1718db97 100644 --- a/app/services/issues/list_query_service.rb +++ b/app/services/issues/list_query_service.rb @@ -28,7 +28,7 @@ class Issues::ListQueryService < ApplicationService end if search_name.present? - issues = issues.where("subject like ?", "%#{search_name}%") + issues = issues.where("subject LIKE ? OR description LIKE ? ", "%#{search_name}%", "%#{search_name}%") end if start_time&.present? || end_time&.present? diff --git a/app/services/organizations/create_service.rb b/app/services/organizations/create_service.rb index 96bb3f46..a430ed6f 100644 --- a/app/services/organizations/create_service.rb +++ b/app/services/organizations/create_service.rb @@ -54,7 +54,7 @@ class Organizations::CreateService < ApplicationService end def create_owner_info - @owner_team = Team.build(organization.id, "Owners", "", 4, true, true) + @owner_team = Team.build(organization.id, "Owners", "Owner团队", "", 4, true, true) TeamUnit.unit_types.keys.each do |u_type| TeamUnit.build(organization.id, owner_team.id, u_type) end diff --git a/app/services/organizations/teams/create_service.rb b/app/services/organizations/teams/create_service.rb index 81ad7877..c3fc5f59 100644 --- a/app/services/organizations/teams/create_service.rb +++ b/app/services/organizations/teams/create_service.rb @@ -28,6 +28,10 @@ class Organizations::Teams::CreateService < ApplicationService params[:name] end + def nickname + params[:nickname] + end + def description params[:description] end @@ -45,7 +49,7 @@ class Organizations::Teams::CreateService < ApplicationService end def create_team - @team = Team.build(org.id, name, description, authorize, + @team = Team.build(org.id, name, nickname, description, authorize, includes_all_project, can_create_org_project) end diff --git a/app/services/organizations/teams/update_service.rb b/app/services/organizations/teams/update_service.rb index 71c82c40..b5b273d8 100644 --- a/app/services/organizations/teams/update_service.rb +++ b/app/services/organizations/teams/update_service.rb @@ -27,7 +27,7 @@ class Organizations::Teams::UpdateService < ApplicationService if team.authorize == "owner" update_params = params.slice(:description) else - update_params = params.slice(:name, :description, :authorize, :includes_all_project, :can_create_org_project) + update_params = params.slice(:name, :nickname, :description, :authorize, :includes_all_project, :can_create_org_project) end update_params end diff --git a/app/services/projects/accept_transfer_service.rb b/app/services/projects/accept_transfer_service.rb new file mode 100644 index 00000000..c99885ca --- /dev/null +++ b/app/services/projects/accept_transfer_service.rb @@ -0,0 +1,49 @@ +class Projects::AcceptTransferService < ApplicationService + attr_accessor :applied_transfer_project, :owner + attr_reader :user, :project + + def initialize(user, project) + @user = user + @project = project + @applied_transfer_project = project.applied_transfer_project + @owner = @applied_transfer_project.owner + end + + def call + Rails.logger.info("###### Project accept_transfer_service begin ######") + ActiveRecord::Base.transaction do + validate! + update_apply + operate_project + send_apply_message + end + + Rails.logger.info("##### Project accept_transfer_service end ######") + + + return @applied_transfer_project + end + + private + def validate! + raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering + raise Error, '未拥有接受转移权限' unless is_permit_operator + end + + def is_permit_operator + return true if @user == @owner + return @owner.is_a?(Organization) && @owner.is_admin?(@user) + end + + def update_apply + @applied_transfer_project.update!(status: 'accepted') + end + + def operate_project + @project = Projects::TransferService.call(@project, @owner) + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'successed') + end +end \ No newline at end of file diff --git a/app/services/projects/apply_transfer_service.rb b/app/services/projects/apply_transfer_service.rb new file mode 100644 index 00000000..28097bed --- /dev/null +++ b/app/services/projects/apply_transfer_service.rb @@ -0,0 +1,43 @@ +class Projects::ApplyTransferService < ApplicationService + attr_accessor :owner, :applied_transfer_project + attr_reader :user, :project, :params + + def initialize(user, project, params) + @user = user + @project = project + @params = params + @owner = Owner.find_by(login: params[:owner_name]) + end + + def call + Rails.logger.info("###### Project apply_transfer_service begin ######") + validate! + create_apply + send_apply_message + Rails.logger.info("###### Project apply_transfer_service end ######") + + return @applied_transfer_project + end + + private + def validate! + raise Error, '仓库标识不正确' if @project.identifier != params[:identifier] + raise Error, '该仓库正在迁移' if @project.is_transfering + raise Error, '新拥有者不存在' unless @owner.present? + raise Error, '新拥有者已经存在同名仓库!' if Project.where(user_id: @owner.id, identifier: params[:identifier]).present? + raise Error, '未拥有转移权限' unless is_permit_owner + end + + def is_permit_owner + return true unless @owner.is_a?(Organization) + return @owner.is_owner?(@user) + end + + def create_apply + @applied_transfer_project = AppliedTransferProject.create!(user_id: user.id, project_id: project.id, owner_id: @owner.id) + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'common') + end +end \ No newline at end of file diff --git a/app/services/projects/cancel_transfer_service.rb b/app/services/projects/cancel_transfer_service.rb new file mode 100644 index 00000000..153b24ff --- /dev/null +++ b/app/services/projects/cancel_transfer_service.rb @@ -0,0 +1,33 @@ +class Projects::CancelTransferService < ApplicationService + attr_accessor :applied_transfer_project + attr_reader :user, :project + + def initialize(user, project) + @user = user + @project = project + @applied_transfer_project = project.applied_transfer_project + end + + def call + Rails.logger.info("###### Project cancel_transfer_service begin ######") + validate! + update_apply + send_apply_message + Rails.logger.info("###### Project cancel_transfer_service end ######") + + return @applied_transfer_project + end + + private + def validate! + raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering + end + + def update_apply + @applied_transfer_project.update!(status: 'canceled') + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'canceled') + end +end \ No newline at end of file diff --git a/app/services/projects/migrate_service.rb b/app/services/projects/migrate_service.rb index d1e14088..7df08f9e 100644 --- a/app/services/projects/migrate_service.rb +++ b/app/services/projects/migrate_service.rb @@ -1,5 +1,6 @@ class Projects::MigrateService < ApplicationService attr_reader :user, :params + attr_accessor :project def initialize(user, params) @user = user @@ -9,8 +10,9 @@ class Projects::MigrateService < ApplicationService def call @project = Project.new(project_params) if @project.save! - ProjectUnit.init_types(@project.id) + ProjectUnit.init_types(@project.id, project.project_type) Project.update_mirror_projects_count! + @project.set_owner_permission(user) Repositories::MigrateService.new(user, @project, repository_params).call else # @@ -48,7 +50,8 @@ class Projects::MigrateService < ApplicationService user_id: params[:user_id], login: params[:auth_username], password: params[:auth_password], - is_mirror: params[:is_mirror] + is_mirror: params[:is_mirror], + source_clone_url: params[:source_clone_url] } end diff --git a/app/services/projects/refuse_transfer_service.rb b/app/services/projects/refuse_transfer_service.rb new file mode 100644 index 00000000..1b81b574 --- /dev/null +++ b/app/services/projects/refuse_transfer_service.rb @@ -0,0 +1,40 @@ +class Projects::RefuseTransferService < ApplicationService + attr_accessor :applied_transfer_project, :owner + attr_reader :user, :project + + def initialize(user, project) + @user = user + @project = project + @applied_transfer_project = project.applied_transfer_project + @owner = @applied_transfer_project.owner + end + + def call + Rails.logger.info("###### Project refuse_transfer_service begin ######") + validate! + update_apply + send_apply_message + Rails.logger.info("###### Project refuse_transfer_service end ######") + + return @applied_transfer_project + end + + private + def validate! + raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering + raise Error, '未拥有拒绝转移权限' unless is_permit_operator + end + + def is_permit_operator + return true if @user == @owner + return @owner.is_a?(Organization) && @owner.is_admin?(@user) + end + + def update_apply + @applied_transfer_project.update!(status: 'refused') + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'failure') + end +end \ No newline at end of file diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index b32b2481..a2da34f0 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -23,6 +23,7 @@ class Projects::TransferService < ApplicationService private def update_owner + project.members.find_by(user_id: owner.id).destroy! if owner.is_a?(User) project.update!(user_id: new_owner.id) end @@ -32,9 +33,8 @@ class Projects::TransferService < ApplicationService 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 + # 为包含组织所有项目的团队创建项目访问权限 + new_owner.build_permit_team_projects(project.id) else project.team_projects.each(&:destroy!) end diff --git a/app/services/pull_requests/merge_service.rb b/app/services/pull_requests/merge_service.rb index a20ee3aa..d5fc102e 100644 --- a/app/services/pull_requests/merge_service.rb +++ b/app/services/pull_requests/merge_service.rb @@ -1,6 +1,6 @@ class PullRequests::MergeService < ApplicationService attr_reader :owner, :repo, :pull, :current_user, :params - + attr_accessor :status, :message # eq: # PullRequests::MergeService.call(owner, repo, pull, current_user, params) def initialize(owner, repo, pull, current_user, params) @@ -15,6 +15,7 @@ class PullRequests::MergeService < ApplicationService ActiveRecord::Base.transaction do gitea_pull_merge! end + self end private @@ -22,8 +23,7 @@ class PullRequests::MergeService < ApplicationService def gitea_pull_merge! result = Gitea::PullRequest::MergeService.call(@current_user.gitea_token, @owner.login, @repo.identifier, @pull.gpid, gitea_merge_pull_params) - - result[:status] === 200 ? true : false + @status, @message = result end def gitea_merge_pull_params diff --git a/app/services/repositories/create_service.rb b/app/services/repositories/create_service.rb index 18e72644..f0c584e1 100644 --- a/app/services/repositories/create_service.rb +++ b/app/services/repositories/create_service.rb @@ -15,6 +15,7 @@ class Repositories::CreateService < ApplicationService create_gitea_repository sync_project sync_repository + @project.set_owner_permission(user) # if project.project_type == "common" # chain_params = { # type: "create", @@ -44,19 +45,9 @@ class Repositories::CreateService < ApplicationService @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( diff --git a/app/services/repositories/migrate_service.rb b/app/services/repositories/migrate_service.rb index 156777cc..374115bf 100644 --- a/app/services/repositories/migrate_service.rb +++ b/app/services/repositories/migrate_service.rb @@ -21,7 +21,7 @@ class Repositories::MigrateService < ApplicationService private def repository_params - params.merge(project_id: project.id, identifier: params[:identifier]) + params.merge(project_id: project.id) end def gitea_repository_params diff --git a/app/views/admins/faqs/_form_modal.html.erb b/app/views/admins/faqs/_form_modal.html.erb new file mode 100644 index 00000000..45ceb901 --- /dev/null +++ b/app/views/admins/faqs/_form_modal.html.erb @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/app/views/admins/faqs/_list.html.erb b/app/views/admins/faqs/_list.html.erb new file mode 100644 index 00000000..81e658ed --- /dev/null +++ b/app/views/admins/faqs/_list.html.erb @@ -0,0 +1,33 @@ + + + + + + + + + + + + + <% if faqs.present? %> + <% faqs.each_with_index do |faq, index| %> + + + + + + + + + <% end %> + <% else %> + <%= render 'admins/shared/no_data_for_table' %> + <% end %> + +
序号标题url<%= sort_tag('创建于', name: 'created_at', path: admins_faqs_path) %><%= sort_tag('更新于', name: 'updated_at', path: admins_faqs_path) %>操作
<%= list_index_no((params[:page] || 1).to_i, index) %><%= faq.question%><%= link_to faq.url, target: '_blank' %><%= display_text(faq.created_at&.strftime('%Y-%m-%d %H:%M')) %><%= display_text(faq.updated_at&.strftime('%Y-%m-%d %H:%M')) %> + <%= link_to "编辑", edit_admins_faq_path(faq), remote: true, class: "action" %> + <%= link_to "删除", admins_faqs_path(faq.id), method: :delete, data:{confirm: "确认删除的吗?"}, class: "delete-project-action" %> +
+ +<%= render partial: 'admins/shared/paginate', locals: { objects: faqs } %> diff --git a/app/views/admins/faqs/edit.js.erb b/app/views/admins/faqs/edit.js.erb new file mode 100644 index 00000000..fc121b18 --- /dev/null +++ b/app/views/admins/faqs/edit.js.erb @@ -0,0 +1,2 @@ +$("#faq-modals").html("<%= j render(partial: 'admins/faqs/form_modal', locals: {type: 'update'}) %>") +$(".faq-change-modal").modal('show'); \ No newline at end of file diff --git a/app/views/admins/faqs/index.html.erb b/app/views/admins/faqs/index.html.erb new file mode 100644 index 00000000..3b327249 --- /dev/null +++ b/app/views/admins/faqs/index.html.erb @@ -0,0 +1,17 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('FAQ', admins_faqs_path) %> +<% end %> + +
+ <%= form_tag(admins_faqs_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %> + <%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '输入标题搜素') %> + <%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %> + <% end %> + <%= link_to "新增", new_admins_faq_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %> +
+ +
+ <%= render partial: 'admins/faqs/list', locals: { faqs: @faqs } %> +
+
+
\ No newline at end of file diff --git a/app/views/admins/faqs/index.js.erb b/app/views/admins/faqs/index.js.erb new file mode 100644 index 00000000..ebb1900e --- /dev/null +++ b/app/views/admins/faqs/index.js.erb @@ -0,0 +1 @@ +$('.faqs-list-container').html("<%= j( render partial: 'admins/faqs/list', locals: { faqs: @faqs } ) %>"); \ No newline at end of file diff --git a/app/views/admins/faqs/new.js.erb b/app/views/admins/faqs/new.js.erb new file mode 100644 index 00000000..edeb83a8 --- /dev/null +++ b/app/views/admins/faqs/new.js.erb @@ -0,0 +1,2 @@ +$("#faq-modals").html("<%= j render(partial: 'admins/faqs/form_modal', locals: {type: 'create'}) %>") +$(".faq-change-modal").modal('show'); \ No newline at end of file diff --git a/app/views/admins/projects/shared/_list.html.erb b/app/views/admins/projects/shared/_list.html.erb index bd630028..fe8d96e9 100644 --- a/app/views/admins/projects/shared/_list.html.erb +++ b/app/views/admins/projects/shared/_list.html.erb @@ -23,7 +23,7 @@ <%= list_index_no((params[:page] || 1).to_i, index) %> <%= project.id %> - <%= link_to(project.name, "/projects/#{project.id}", target: '_blank') %> + <%= link_to(project.name, "/projects/#{project&.owner&.login}/#{project.identifier}", target: '_blank') %> <%= project.is_public ? '√' : '' %> <%= project.issues.size %> @@ -33,7 +33,7 @@ <%= project.versions.size %> <%= project.members.size %> - <%= project.owner ? link_to(project.owner&.real_name, "/users/#{project.owner&.login}", target: '_blank') : "" %> + <%= link_to_project(project) %> <%= project.created_on&.strftime('%Y-%m-%d %H:%M') %> diff --git a/app/views/admins/shared/_sidebar.html.erb b/app/views/admins/shared/_sidebar.html.erb index de56a547..ad53c661 100644 --- a/app/views/admins/shared/_sidebar.html.erb +++ b/app/views/admins/shared/_sidebar.html.erb @@ -38,6 +38,7 @@
  • <%= sidebar_item(admins_cooperatives_path, '合作伙伴', icon: 'handshake-o', controller: 'admins-cooperatives') %>
  • <%= sidebar_item(edit_admins_agreement_path, '服务协议', icon: 'file-text-o', controller: 'admins-agreements') %>
  • <%= sidebar_item(edit_admins_help_center_path, '帮助中心', icon: 'question-circle-o', controller: 'admins-help_centers') %>
  • +
  • <%= sidebar_item(admins_faqs_path, 'FAQ', icon: 'question-circle', controller: 'admins-faqs') %>
  • <% end %>
  • diff --git a/app/views/compare/show.json.jbuilder b/app/views/compare/show.json.jbuilder index 8257cc7f..8b75fa3d 100644 --- a/app/views/compare/show.json.jbuilder +++ b/app/views/compare/show.json.jbuilder @@ -4,7 +4,7 @@ json.commits do json.array! @compare_result['Commits'] do |commit| json.author do # TODO: 获取头像地址待优化 - forge_user = User.includes(:user_extension).select(:id, :login).find_by(login: commit['Author']['Name']) + forge_user = User.includes(:user_extension).find_by(login: commit['Author']['Name']) json.login commit['Author']['Name'] json.name commit['Author']['Name'] json.image_url forge_user.nil? ? '' : url_to_avatar(forge_user) @@ -12,7 +12,7 @@ json.commits do json.committer do # TODO: 获取头像地址待优化 - forge_user = User.includes(:user_extension).select(:id, :login).find_by(login: commit['Committer']['Name']) + forge_user = User.includes(:user_extension).find_by(login: commit['Committer']['Name']) json.login commit['Committer']['Name'] json.name commit['Committer']['Name'] json.image_url forge_user.nil? ? '' : url_to_avatar(forge_user) diff --git a/app/views/organizations/organizations/_detail.json.jbuilder b/app/views/organizations/organizations/_detail.json.jbuilder index 64feadca..4a8b512b 100644 --- a/app/views/organizations/organizations/_detail.json.jbuilder +++ b/app/views/organizations/organizations/_detail.json.jbuilder @@ -1,6 +1,6 @@ json.id organization.id json.name organization.login -json.nickname organization.nickname +json.nickname organization.nickname.blank? ? organization.name : organization.nickname json.description organization.description json.website organization.website json.location organization.location diff --git a/app/views/organizations/organizations/_simple.json.jbuilder b/app/views/organizations/organizations/_simple.json.jbuilder new file mode 100644 index 00000000..12792b12 --- /dev/null +++ b/app/views/organizations/organizations/_simple.json.jbuilder @@ -0,0 +1,5 @@ +json.id organization.id +json.name organization.login +json.nickname organization.nickname.blank? ? organization.name : organization.nickname +json.description organization.description +json.avatar_url url_to_avatar(organization) diff --git a/app/views/organizations/organizations/recommend.json.jbuilder b/app/views/organizations/organizations/recommend.json.jbuilder new file mode 100644 index 00000000..262cc23d --- /dev/null +++ b/app/views/organizations/organizations/recommend.json.jbuilder @@ -0,0 +1,6 @@ +json.organizations @organizations do |organization| + json.id organization.id + json.name organization.login + json.nickname organization.nickname.blank? ? organization.name : organization.nickname + json.avatar_url url_to_avatar(organization) +end \ No newline at end of file diff --git a/app/views/organizations/team_projects/_detail.json.jbuilder b/app/views/organizations/team_projects/_detail.json.jbuilder index 9af32d1d..dde70700 100644 --- a/app/views/organizations/team_projects/_detail.json.jbuilder +++ b/app/views/organizations/team_projects/_detail.json.jbuilder @@ -1,6 +1,7 @@ json.id team_project.id json.project do - json.owner_name team_project&.project&.owner&.login + json.owner_login team_project&.project&.owner&.login + json.owner_name team_project&.project&.owner&.real_name json.owner_image_url url_to_avatar(team_project&.project&.owner) json.name team_project&.project&.name json.identifier team_project&.project&.identifier diff --git a/app/views/organizations/teams/_detail.json.jbuilder b/app/views/organizations/teams/_detail.json.jbuilder index 4f3588cb..68572814 100644 --- a/app/views/organizations/teams/_detail.json.jbuilder +++ b/app/views/organizations/teams/_detail.json.jbuilder @@ -1,5 +1,6 @@ json.id team.id json.name team.name +json.nickname team.nickname.blank? ? team.name : team.nickname json.description team.description json.authorize team.authorize json.includes_all_project team.includes_all_project diff --git a/app/views/projects/_project_detail.json.jbuilder b/app/views/projects/_project_detail.json.jbuilder index 94aee427..d0b62aaa 100644 --- a/app/views/projects/_project_detail.json.jbuilder +++ b/app/views/projects/_project_detail.json.jbuilder @@ -26,7 +26,7 @@ json.author do json.name user.try(:show_real_name) json.type user&.type json.login user.login - json.image_url render_avatar_url(user) + json.image_url url_to_avatar(user) end end diff --git a/app/views/projects/applied_transfer_projects/_detail.json.jbuilder b/app/views/projects/applied_transfer_projects/_detail.json.jbuilder new file mode 100644 index 00000000..e583ed1d --- /dev/null +++ b/app/views/projects/applied_transfer_projects/_detail.json.jbuilder @@ -0,0 +1,21 @@ +project = object.project +json.project do + json.id project.id + json.identifier project.identifier + json.name project.name + json.description project.description + json.is_public project.is_public + json.owner do + json.partial! "/users/user_simple", locals: {user: project.owner} + end +end +json.user do + json.partial! "/users/user_simple", locals: {user: object.user} +end +json.owner do + json.partial! "/users/user_simple", locals: {user: object.owner} +end +json.id object.id +json.status object.status +json.created_at format_time(object.created_at) +json.time_ago time_from_now(object.created_at) diff --git a/app/views/projects/applied_transfer_projects/cancel.json.jbuilder b/app/views/projects/applied_transfer_projects/cancel.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/projects/applied_transfer_projects/cancel.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/projects/applied_transfer_projects/create.json.jbuilder b/app/views/projects/applied_transfer_projects/create.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/projects/applied_transfer_projects/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/projects/applied_transfer_projects/organizations.json.jbuilder b/app/views/projects/applied_transfer_projects/organizations.json.jbuilder new file mode 100644 index 00000000..772910f0 --- /dev/null +++ b/app/views/projects/applied_transfer_projects/organizations.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @organizations.size +json.organizations @organizations do |org| + json.partial! "/organizations/organizations/simple", locals: {organization: org} +end diff --git a/app/views/projects/index.json.jbuilder b/app/views/projects/index.json.jbuilder index 0e23ac03..96ab89c0 100644 --- a/app/views/projects/index.json.jbuilder +++ b/app/views/projects/index.json.jbuilder @@ -28,7 +28,7 @@ json.projects @projects do |project| json.type user.type json.name user.try(:show_real_name) json.login user.login - json.image_url render_avatar_url(user) + json.image_url url_to_avatar(user) end end diff --git a/app/views/projects/update.json.jbuilder b/app/views/projects/update.json.jbuilder index ef09d5d8..01e70377 100644 --- a/app/views/projects/update.json.jbuilder +++ b/app/views/projects/update.json.jbuilder @@ -5,4 +5,5 @@ json.description @project.description json.project_category_id @project.project_category_id json.project_language_id @project.project_language_id json.is_public @project.is_public -json.website @project.website \ No newline at end of file +json.website @project.website +json.lesson_url @project.lesson_url \ No newline at end of file diff --git a/app/views/pull_requests/_commit.json.jbuilder b/app/views/pull_requests/_commit.json.jbuilder index 072e53c9..7a9232ef 100644 --- a/app/views/pull_requests/_commit.json.jbuilder +++ b/app/views/pull_requests/_commit.json.jbuilder @@ -1,10 +1,10 @@ json.author do - author = User.find_by(login: commit['Author']['Name']) + author = User.find_by(mail: commit['Author']['Email']) json.partial! 'repositories/commit_author', locals: { user: author, name: commit['Committer']['Name'] } end json.committer do - author = User.find_by(login: commit['Committer']['Name']) + author = User.find_by(mail: commit['Committer']['Email']) json.partial! 'repositories/commit_author', locals: { user: author, name: commit['Committer']['Name'] } end json.timestamp render_unix_time(commit['Committer']['When']) diff --git a/app/views/pull_requests/_merge_item.json.jbuilder b/app/views/pull_requests/_merge_item.json.jbuilder index 8475c468..0db17e26 100644 --- a/app/views/pull_requests/_merge_item.json.jbuilder +++ b/app/views/pull_requests/_merge_item.json.jbuilder @@ -5,10 +5,10 @@ json.issue_priories @project_priories json.project_author @project.owner.try(:show_real_name) json.project_name @project.try(:name) json.members do - json.array! @project_members.to_a.each do |member| - json.id member.user_id - json.login member.user.try(:login) - json.name member.user.try(:show_real_name) - json.avatar_url url_to_avatar(member.user) + json.array! @project_members.to_a.each do |user| + json.id user.id + json.login user.try(:login) + json.name user.try(:show_real_name) + json.avatar_url url_to_avatar(user) end end \ No newline at end of file diff --git a/app/views/pull_requests/show.json.jbuilder b/app/views/pull_requests/show.json.jbuilder index c3f781f7..9c268186 100644 --- a/app/views/pull_requests/show.json.jbuilder +++ b/app/views/pull_requests/show.json.jbuilder @@ -12,6 +12,8 @@ json.pull_request do json.pull_request_staus @pull_request.status == 1 ? "merged" : (@pull_request.status == 2 ? "closed" : "open") json.fork_project_user @pull_request&.fork_project&.owner.try(:login) json.create_user @pull_request&.user&.login + json.mergeable @gitea_pull["mergeable"] + json.state @gitea_pull["state"] end json.issue do diff --git a/app/views/repositories/_commit_author.json.jbuilder b/app/views/repositories/_commit_author.json.jbuilder index d57e7c53..1478cca0 100644 --- a/app/views/repositories/_commit_author.json.jbuilder +++ b/app/views/repositories/_commit_author.json.jbuilder @@ -7,5 +7,5 @@ else json.id nil json.login name json.name name - json.image_url File.join("avatars/User","b") + json.image_url User::Avatar.get_letter_avatar_url(name) end diff --git a/app/views/repositories/commits.json.jbuilder b/app/views/repositories/commits.json.jbuilder index 82cd1713..16744858 100644 --- a/app/views/repositories/commits.json.jbuilder +++ b/app/views/repositories/commits.json.jbuilder @@ -5,12 +5,15 @@ else json.total_count @hash_commit[:total_count] json.commits do json.array! @hash_commit[:body] do |commit| - json.commit1 commit commiter = commit['committer'] - if commiter.present? - commit_user_id = commiter['id'] - forge_user = User.simple_select.find_by(gitea_uid: commit_user_id) - end + + forge_user = + if commiter.present? + User.simple_select.find_by(mail: commiter['email']) + else + User.simple_select.find_by(mail: commit['commit']['committer']['email']) + end + json.sha commit['sha'] json.message commit['commit']['message'] json.timestamp render_unix_time(commit['commit']['author']['date']) @@ -23,7 +26,7 @@ else json.login commit['commit']['author']['name'] json.type nil json.name commit['commit']['author']['name'] - json.image_url File.join("avatars/User","b") + json.image_url User::Avatar.get_letter_avatar_url(commit['commit']['author']['name']) end end end diff --git a/app/views/repositories/detail.json.jbuilder b/app/views/repositories/detail.json.jbuilder index 28a64e50..39f2563a 100644 --- a/app/views/repositories/detail.json.jbuilder +++ b/app/views/repositories/detail.json.jbuilder @@ -1,5 +1,6 @@ json.content @project.content json.website @project.website +json.lesson_url @project.lesson_url if @result[:readme].blank? json.readme nil else @@ -20,7 +21,7 @@ json.versions_count @project.versions_count #里程碑数量 json.version_releases_count @project.releases_size(@user.try(:id), "all") json.version_releasesed_count @project.releases_size(@user.try(:id), "released") #已发行的版本 json.permission render_permission(@user, @project) -json.mirror_url @project&.repository.mirror_url +json.mirror_url @project&.repository.remote_mirror_url json.mirror @project&.repository.mirror_url.present? json.type @project.numerical_for_project_type json.open_devops @project.open_devops? @@ -79,7 +80,7 @@ json.contributors do total_count = @result[:contributor].size json.list @result[:contributor].each do |contributor| user = User.find_by(gitea_uid: contributor["id"]) - if contributor["login"] == "root" + if contributor["login"] == "root" || user.nil? total_count -= 1 next end diff --git a/app/views/repositories/edit.json.jbuilder b/app/views/repositories/edit.json.jbuilder index 328bc490..7a11411f 100644 --- a/app/views/repositories/edit.json.jbuilder +++ b/app/views/repositories/edit.json.jbuilder @@ -6,4 +6,10 @@ json.project_category_id @project.project_category_id json.project_language_id @project.project_language_id json.private !@project.is_public json.website @project.website -json.project_units @project.project_units.pluck(:unit_type) \ No newline at end of file +json.project_units @project.project_units.pluck(:unit_type) +json.lesson_url @project.lesson_url +json.permission render_permission(current_user, @project) +json.is_transfering @project.is_transfering +json.transfer do + json.partial! "/users/user_simple", locals: {user: @project&.applied_transfer_project&.owner} +end \ No newline at end of file diff --git a/app/views/repositories/entries.json.jbuilder b/app/views/repositories/entries.json.jbuilder index 6964c1c5..a12c088b 100644 --- a/app/views/repositories/entries.json.jbuilder +++ b/app/views/repositories/entries.json.jbuilder @@ -59,4 +59,6 @@ if @project.forge? end end end + + json.readme @readme.merge(content: readme_render_decode64_content(@readme["content"], nil)) end diff --git a/app/views/users/_user_simple.json.jbuilder b/app/views/users/_user_simple.json.jbuilder index 3f9feebb..5da16150 100644 --- a/app/views/users/_user_simple.json.jbuilder +++ b/app/views/users/_user_simple.json.jbuilder @@ -1,4 +1,9 @@ -json.id user.id -json.name user.real_name -json.login user.login -json.image_url url_to_avatar(user) \ No newline at end of file +if user.present? + json.id user.id + json.type user.type + json.name user.real_name + json.login user.login + json.image_url url_to_avatar(user) +else + json.nil! +end \ No newline at end of file diff --git a/app/views/users/applied_messages/_detail.json.jbuilder b/app/views/users/applied_messages/_detail.json.jbuilder new file mode 100644 index 00000000..cca202c1 --- /dev/null +++ b/app/views/users/applied_messages/_detail.json.jbuilder @@ -0,0 +1,26 @@ +# project = object.project +# json.project do +# json.id project.id +# json.identifier project.identifier +# json.name project.name +# json.description project.description +# json.is_public project.is_public +# json.owner do +# json.partial! "/users/user_simple", locals: {user: project.owner} +# end +# end +# json.user do +# json.partial! "/users/user_simple", locals: {user: object.user} +# end +json.applied do + json.partial! "/projects/applied_transfer_projects/detail", locals: {object: object.applied} +end +json.applied_user do + json.partial! "/users/user_simple", locals: {user: object.applied_user} +end +json.applied_type object.applied_type +json.name object.name +json.viewed object.viewed +json.status object.status +json.created_at format_time(object.created_at) +json.time_ago time_from_now(object.created_at) diff --git a/app/views/users/applied_messages/index.json.jbuilder b/app/views/users/applied_messages/index.json.jbuilder new file mode 100644 index 00000000..75647566 --- /dev/null +++ b/app/views/users/applied_messages/index.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @applied_messages.total_count +json.applied_messages @applied_messages do |message| + json.partial! "/users/applied_messages/detail", locals: {object: message} +end diff --git a/app/views/users/applied_transfer_projects/accept.json.jbuilder b/app/views/users/applied_transfer_projects/accept.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/users/applied_transfer_projects/accept.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/users/applied_transfer_projects/index.json.jbuilder b/app/views/users/applied_transfer_projects/index.json.jbuilder new file mode 100644 index 00000000..caf9d1ad --- /dev/null +++ b/app/views/users/applied_transfer_projects/index.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @applied_transfer_projects.total_count +json.applied_transfer_projects @applied_transfer_projects do |apply| + json.partial! "/projects/applied_transfer_projects/detail", locals: {object: apply} +end diff --git a/app/views/users/applied_transfer_projects/refuse.json.jbuilder b/app/views/users/applied_transfer_projects/refuse.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/users/applied_transfer_projects/refuse.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/users/courses/index.json.jbuilder b/app/views/users/courses/index.json.jbuilder deleted file mode 100644 index 614e0639..00000000 --- a/app/views/users/courses/index.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ - -json.count @count -json.courses @courses, partial: 'users/courses/shared/course', as: :course diff --git a/app/views/users/courses/shared/_course.json.jbuilder b/app/views/users/courses/shared/_course.json.jbuilder deleted file mode 100644 index a0c5ddc7..00000000 --- a/app/views/users/courses/shared/_course.json.jbuilder +++ /dev/null @@ -1,18 +0,0 @@ -json.id course.id -json.name course.name -# json.members_count course.members_count -json.members_count course.course_members_count -# json.homework_commons_count course.homework_commons_count -json.homework_commons_count get_tasks_count course -json.attachments_count course.attachments.count -json.visits course.visits -json.school course.school&.name - -json.first_category_url module_url(course.course_modules.where(hidden: 0).order(position: :desc).first, course) - -json.is_public course.is_public -json.can_visited observed_logged_user? || course.can_visited? - -json.teacher do - json.partial! 'users/shared/real_user', user: course.teacher -end \ No newline at end of file diff --git a/app/views/users/hovercard.json.jbuilder b/app/views/users/hovercard.json.jbuilder new file mode 100644 index 00000000..79f1793f --- /dev/null +++ b/app/views/users/hovercard.json.jbuilder @@ -0,0 +1,20 @@ +json.id @user.id +json.login @user.login +json.name @user.full_name +json.location @user.location +json.image_url url_to_avatar(@user) +json.url "#{request.base_url }/users/#{@user.login}" +json.followers_count @user.followers_count +json.followers_url "#{base_url}/users/#{@user.login}/fan_users" +json.following_count @user.following_count +json.following_url "#{base_url}/users/#{@user.login}/watchers" +json.projects_count @user.projects_count +json.projects_url "#{base_url}/users/#{@user.login}" +json.projects_count @user.projects_count +json.is_watch current_user&.watched?(@user) +json.organizations @user.organizations do |organization| + json.login organization.login + json.name organization.real_name + json.image_url url_to_avatar(organization) + json.url "#{base_url}/organize/#{organization.login}" +end \ No newline at end of file diff --git a/app/views/users/shixuns/index.json.jbuilder b/app/views/users/shixuns/index.json.jbuilder deleted file mode 100644 index a5321f1c..00000000 --- a/app/views/users/shixuns/index.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ - -json.count @count -json.shixuns @shixuns, partial: 'users/shixuns/shared/shixun', as: :shixun, locals: { user: observed_user } diff --git a/app/views/users/shixuns/shared/_shixun.json.jbuilder b/app/views/users/shixuns/shared/_shixun.json.jbuilder deleted file mode 100644 index 663522e5..00000000 --- a/app/views/users/shixuns/shared/_shixun.json.jbuilder +++ /dev/null @@ -1,10 +0,0 @@ -json.id shixun.id -json.identifier shixun.identifier -json.tag shixun.first_tag_repertoire&.name -json.image_url url_to_avatar(shixun) -json.name shixun.name -json.status shixun.status -json.human_status shixun.human_status -json.challenges_count shixun.challenges_count -json.finished_challenges_count @finished_challenges_count_map&.fetch(shixun.id, 0) || shixun.finished_challenges_count(user) -json.is_jupyter shixun.is_jupyter \ No newline at end of file diff --git a/app/views/users/show.json.jbuilder b/app/views/users/show.json.jbuilder index 54f85c5c..e80ac749 100644 --- a/app/views/users/show.json.jbuilder +++ b/app/views/users/show.json.jbuilder @@ -10,6 +10,8 @@ json.user_identity @user.identity json.is_watch current_user&.watched?(@user) json.watched_count @user.fan_count #粉丝 json.watching_count @user.follow_count #关注数 +json.undo_messages @waiting_applied_messages.size +json.undo_transfer_projects @common_applied_transfer_projects.size json.undo_events @undo_events json.user_composes_count @user_composes_count json.user_org_count @user_org_count diff --git a/app/views/users/videos/_video.json.jbuilder b/app/views/users/videos/_video.json.jbuilder deleted file mode 100644 index 1e09ca4b..00000000 --- a/app/views/users/videos/_video.json.jbuilder +++ /dev/null @@ -1,6 +0,0 @@ -json.extract! video, :id, :title, :cover_url, :file_url, :play_url, :vv, :user_id - -json.play_duration video.video_play_duration -json.published_at video.display_published_at -json.created_at video.display_created_at -json.updated_at video.display_updated_at \ No newline at end of file diff --git a/app/views/users/videos/index.json.jbuilder b/app/views/users/videos/index.json.jbuilder deleted file mode 100644 index f4a34aa0..00000000 --- a/app/views/users/videos/index.json.jbuilder +++ /dev/null @@ -1,2 +0,0 @@ -json.count @count -json.videos @videos, partial: 'video', as: :video \ No newline at end of file diff --git a/app/views/users/videos/review.json.jbuilder b/app/views/users/videos/review.json.jbuilder deleted file mode 100644 index 3e0fe8b9..00000000 --- a/app/views/users/videos/review.json.jbuilder +++ /dev/null @@ -1,7 +0,0 @@ -json.count @count -json.videos do - json.array! @videos.each do |video| - json.partial! 'video', video: video - json.file_url nil - end -end \ No newline at end of file diff --git a/app/views/users/videos/update.json.jbuilder b/app/views/users/videos/update.json.jbuilder deleted file mode 100644 index ed69a174..00000000 --- a/app/views/users/videos/update.json.jbuilder +++ /dev/null @@ -1 +0,0 @@ -json.partial! 'video', video: current_video \ No newline at end of file diff --git a/app/views/version_releases/index.json.jbuilder b/app/views/version_releases/index.json.jbuilder index b9083089..f1730ffa 100644 --- a/app/views/version_releases/index.json.jbuilder +++ b/app/views/version_releases/index.json.jbuilder @@ -17,8 +17,8 @@ json.releases do json.tarball_url re["tarball_url"] json.zipball_url re["zipball_url"] json.draft re["draft"] ? "草稿" : (re["prerelease"] ? "预发行" : "稳定") - json.created_at format_time(re["created_at"].to_s.to_time) - json.published_at format_time(re["published_at"].to_s.to_time) + json.created_at format_time(version.created_at.to_s.to_time) + json.published_at format_time(version.created_at.to_s.to_time) json.user_name user.present? ? user.try(:show_real_name) : "" json.image_url user.present? ? url_to_avatar(user) : "" else @@ -33,8 +33,8 @@ json.releases do json.tarball_url re["tarball_url"] json.zipball_url re["zipball_url"] json.draft re["draft"] ? "草稿" : (re["prerelease"] ? "预发行" : "稳定") - json.created_at format_time(re["created_at"].to_s.to_time) - json.published_at format_time(re["published_at"].to_s.to_time) + json.created_at format_time(version.created_at.to_s.to_time) + json.published_at format_time(version.created_at.to_s.to_time) json.user_name user.present? ? user.try(:show_real_name) : "" json.image_url user.present? ? url_to_avatar(user) : "" end diff --git a/app/views/weapps/code_sessions/create.json.jbuilder b/app/views/weapps/code_sessions/create.json.jbuilder deleted file mode 100644 index 28008694..00000000 --- a/app/views/weapps/code_sessions/create.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -json.user do - json.partial! 'weapps/shared/user', locals: { user: current_user } -end \ No newline at end of file diff --git a/app/views/weapps/courses/basic_info.json.jbuilder b/app/views/weapps/courses/basic_info.json.jbuilder deleted file mode 100644 index 09639072..00000000 --- a/app/views/weapps/courses/basic_info.json.jbuilder +++ /dev/null @@ -1,8 +0,0 @@ -json.course do - json.(@course, :id, :name) - json.code_halt @course.invite_code_halt == 1 - json.invite_code @course.invite_code_halt == 0 ? @course.generate_invite_code : "" - json.teacher_name @course.teacher.real_name - json.teacher_img url_to_avatar(@course.teacher) - json.teacher_school @course.school.try(:name) -end \ No newline at end of file diff --git a/app/views/weapps/courses/course_activities.json.jbuilder b/app/views/weapps/courses/course_activities.json.jbuilder deleted file mode 100644 index 498189cf..00000000 --- a/app/views/weapps/courses/course_activities.json.jbuilder +++ /dev/null @@ -1,12 +0,0 @@ -json.activities @activities do |activity| - json.(activity, :course_act_id, :course_act_type) - json.author do - user = activity.user - json.name user.real_name - json.login user.login - json.img url_to_avatar(user) - end - json.created_at activity.created_at.strftime('%m-%d %H:%M:') - json.container_name activity.container_name - json.container_type activity.course_act_type == "HomeworkCommon" ? activity.course_act&.homework_type : "" -end \ No newline at end of file diff --git a/app/views/weapps/courses/edit.json.jbuilder b/app/views/weapps/courses/edit.json.jbuilder deleted file mode 100644 index 1b7d5aaa..00000000 --- a/app/views/weapps/courses/edit.json.jbuilder +++ /dev/null @@ -1,2 +0,0 @@ -json.(@course, :id, :name, :credit, :end_date) -json.course_list_name @course.course_list&.name \ No newline at end of file diff --git a/app/views/weapps/courses/shixun_homework_category.json.jbuilder b/app/views/weapps/courses/shixun_homework_category.json.jbuilder deleted file mode 100644 index b3874859..00000000 --- a/app/views/weapps/courses/shixun_homework_category.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -json.categories @categories.each do |category| - json.(category, :id, :name) -end \ No newline at end of file diff --git a/app/views/weapps/courses/show.json.jbuilder b/app/views/weapps/courses/show.json.jbuilder deleted file mode 100644 index c22b2442..00000000 --- a/app/views/weapps/courses/show.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.(@course, :id, :name, :course_members_count, :credit, :invite_code_halt) -json.teachers_count @course.teachers.count -json.students_count @course.students.count -json.course_identity @current_user.course_identity(@course) \ No newline at end of file diff --git a/app/views/weapps/courses/students.json.jbuilder b/app/views/weapps/courses/students.json.jbuilder deleted file mode 100644 index 8c51393f..00000000 --- a/app/views/weapps/courses/students.json.jbuilder +++ /dev/null @@ -1,7 +0,0 @@ -json.students student_list @students, @course.excellent, @user_course_identity -json.students_count @students_count -if @course_group - json.course_group do - json.(@course_group, :id, :name, :invite_code, :course_members_count) - end -end \ No newline at end of file diff --git a/app/views/weapps/courses/teachers.json.jbuilder b/app/views/weapps/courses/teachers.json.jbuilder deleted file mode 100644 index 684c1251..00000000 --- a/app/views/weapps/courses/teachers.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -json.teacher_list teacher_list(@teacher_list, @user_course_identity) -json.teacher_list_size @teacher_list_size -json.apply_size @applications_size \ No newline at end of file diff --git a/app/views/weapps/homes/show.json.jbuilder b/app/views/weapps/homes/show.json.jbuilder deleted file mode 100644 index f307d64f..00000000 --- a/app/views/weapps/homes/show.json.jbuilder +++ /dev/null @@ -1,30 +0,0 @@ -json.carousels do - json.array! @carousels do |carousel| - json.extract! carousel, :id, :link, :position - - json.path carousel.link - json.image_url Util::FileManage.source_disk_file_url(carousel) - end -end - -if @advert.present? - json.advert do - json.extract! @advert, :id, :link - json.image_url Util::FileManage.source_disk_file_url(@advert) - end -else - json.advert nil -end - -json.course_count @course_count -json.courses @courses.each do |course| - json.(course, :id, :name, :visits, :course_members_count, :is_end, :invite_code_halt) - json.creator course.teacher.real_name - json.avatar_url url_to_avatar(course.teacher) - json.invite_code course.invite_code_halt == 0 ? course.generate_invite_code : "" - json.school course.school&.name - course_member = @category == "study" ? course.students.where(user_id: @user.id).first : course.teachers.where(user_id: @user.id).first - json.sticky course_member.sticky - json.course_identity current_user.course_identity(course) -end - diff --git a/app/views/weapps/registers/create.json.jbuilder b/app/views/weapps/registers/create.json.jbuilder deleted file mode 100644 index 0cfb5e30..00000000 --- a/app/views/weapps/registers/create.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.status 0 -json.user do - json.partial! 'weapps/shared/user', locals: { user: @user } -end \ No newline at end of file diff --git a/app/views/weapps/searchs/index.json.jbuilder b/app/views/weapps/searchs/index.json.jbuilder deleted file mode 100644 index 11855fb7..00000000 --- a/app/views/weapps/searchs/index.json.jbuilder +++ /dev/null @@ -1,14 +0,0 @@ -json.count @results.total_count -json.results do - json.array! @results.with_highlights(multiple: true) do |obj, highlights| - json.merge! obj.to_searchable_json - json.type obj.class.name.downcase - - json.title highlights.delete(:name)&.join('...') || obj.searchable_title - json.cover url_to_avatar(obj) - - if obj.is_a?(Course) - json.author_avatar_url url_to_avatar(obj.teacher) - end - end -end \ No newline at end of file diff --git a/app/views/weapps/sessions/create.json.jbuilder b/app/views/weapps/sessions/create.json.jbuilder deleted file mode 100644 index 28008694..00000000 --- a/app/views/weapps/sessions/create.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -json.user do - json.partial! 'weapps/shared/user', locals: { user: current_user } -end \ No newline at end of file diff --git a/app/views/weapps/shared/_user.json.jbuilder b/app/views/weapps/shared/_user.json.jbuilder deleted file mode 100644 index 53212ec1..00000000 --- a/app/views/weapps/shared/_user.json.jbuilder +++ /dev/null @@ -1,15 +0,0 @@ -json.username user.full_name -json.real_name user.real_name -json.login user.login -json.user_id user.id -json.image_url url_to_avatar(user) -json.admin user.admin? -json.business user.business? -json.is_teacher user.user_extension&.teacher? -json.user_identity user.identity -json.identity user.user_extension&.identity -json.tidding_count 0 -json.user_phone_binded user.phone.present? -json.phone user.phone -json.profile_completed user.profile_completed? -json.professional_certification user.professional_certification \ No newline at end of file diff --git a/app/views/weapps/unbind_accounts/show.json.jbuilder b/app/views/weapps/unbind_accounts/show.json.jbuilder deleted file mode 100644 index c4223a02..00000000 --- a/app/views/weapps/unbind_accounts/show.json.jbuilder +++ /dev/null @@ -1 +0,0 @@ -json.user_account @user.phone.present? ? @user.phone : (@user.mail.present? ? @user.mail : @user.login) diff --git a/config/configuration.yml.example b/config/configuration.yml.example index 823d8547..b45c9532 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -45,10 +45,16 @@ default: &default signature_key: 'test12345678' gitea: - access_key_id: 'root' - access_key_secret: '_Trustie_10010' - domain: 'https://testgitea.trustie.net' + access_key_id: '' + access_key_secret: '' + domain: 'https://testgit.trustie.net' base_url: '/api/v1' + accelerator: + access_key_id: '' + access_key_secret: '' + access_admin_uid: 1 + domain: 'https://testgit.trustie.net' + base_url: '/api/v1' production: diff --git a/config/gitea_response.yml b/config/gitea_response.yml new file mode 100644 index 00000000..1ff636ac --- /dev/null +++ b/config/gitea_response.yml @@ -0,0 +1,7 @@ +gitea: + pull_request: + merge_service: + 405: + default: "此合并请求有变更与目标分支冲突。" + 'User not allowed to merge PR': "用户没有合并请求的权限" + 403: diff --git a/config/initializers/letter_avatar.rb b/config/initializers/letter_avatar.rb new file mode 100644 index 00000000..d3b0ec09 --- /dev/null +++ b/config/initializers/letter_avatar.rb @@ -0,0 +1,9 @@ +LetterAvatar.setup do |config| + config.fill_color = 'rgba(255, 255, 255, 1)' # default is 'rgba(255, 255, 255, 0.65)' + config.cache_base_path = 'public/system/lets' # default is 'public/system' + config.colors_palette = :iwanthue # default is :google + # config.weight = 500 # default is 300 + config.annotate_position = '-0+10' # default is -0+5 + # config.letters_count = 2 # default is 1 + config.pointsize = 300 +end diff --git a/config/locales/forms/create_issuse_form.zh-CN.yml b/config/locales/forms/create_issuse_form.zh-CN.yml new file mode 100644 index 00000000..643c6866 --- /dev/null +++ b/config/locales/forms/create_issuse_form.zh-CN.yml @@ -0,0 +1,7 @@ +'zh-CN': + activemodel: + attributes: + issues/create_form: + subject: 标题 + issues/update_form: + subject: 标题 \ No newline at end of file diff --git a/config/locales/forms/organizations_create_form.zh-CN.yml b/config/locales/forms/organizations_create_form.zh-CN.yml new file mode 100644 index 00000000..ab43b094 --- /dev/null +++ b/config/locales/forms/organizations_create_form.zh-CN.yml @@ -0,0 +1,9 @@ +'zh-CN': + activemodel: + attributes: + organizations/create_form: + name: 组织账号 + nickname: 组织名称 + location: 组织所在地区 + description: 组织简介 + visibility: 组织可见性 \ No newline at end of file diff --git a/config/locales/forms/organizations_create_team_form.zh-CN.yml b/config/locales/forms/organizations_create_team_form.zh-CN.yml new file mode 100644 index 00000000..ebf40dde --- /dev/null +++ b/config/locales/forms/organizations_create_team_form.zh-CN.yml @@ -0,0 +1,7 @@ +'zh-CN': + activemodel: + attributes: + organizations/create_team_form: + name: 团队标识 + nickname: 团队名称 + description: 团队描述 \ No newline at end of file diff --git a/config/locales/forms/projects_create_form.zh-CN.yml b/config/locales/forms/projects_create_form.zh-CN.yml new file mode 100644 index 00000000..e36a0520 --- /dev/null +++ b/config/locales/forms/projects_create_form.zh-CN.yml @@ -0,0 +1,7 @@ +'zh-CN': + activemodel: + attributes: + projects/create_form: + name: 项目名称 + repository_name: 仓库名称 + description: 项目简介 \ No newline at end of file diff --git a/config/locales/forms/projects_update_form.zh-CN.yml b/config/locales/forms/projects_update_form.zh-CN.yml new file mode 100644 index 00000000..74804f6a --- /dev/null +++ b/config/locales/forms/projects_update_form.zh-CN.yml @@ -0,0 +1,8 @@ +'zh-CN': + activemodel: + attributes: + projects/update_form: + name: 项目名称 + description: 项目简介 + project_category_id: 项目类别 + project_language_id: 项目语言 \ No newline at end of file diff --git a/config/locales/forms/users_login_form.zh-CN.yml b/config/locales/forms/users_login_form.zh-CN.yml new file mode 100644 index 00000000..8b94db42 --- /dev/null +++ b/config/locales/forms/users_login_form.zh-CN.yml @@ -0,0 +1,6 @@ +'zh-CN': + activemodel: + attributes: + users/login_form: + login: 用户名 + password: 密码 \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 24cd6b63..951472eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -130,6 +130,7 @@ Rails.application.routes.draw do get :search end end + get :recommend, on: :collection end end @@ -199,6 +200,7 @@ Rails.application.routes.draw do get :projects get :watch_users get :fan_users + get :hovercard end collection do post :following @@ -258,6 +260,13 @@ Rails.application.routes.draw do end scope module: :users do + resources :applied_messages, only: [:index] + resources :applied_transfer_projects, only: [:index] do + member do + post :accept + post :refuse + end + end resources :organizations, only: [:index] # resources :projects, only: [:index] # resources :subjects, only: [:index] @@ -386,7 +395,6 @@ Rails.application.routes.draw do get :files get :detail get :archive - get :top_counts get :entries match :sub_entries, :via => [:get, :put] get :commits @@ -533,6 +541,12 @@ Rails.application.routes.draw do scope module: :projects do resources :teams, only: [:index, :create, :destroy] resources :project_units, only: [:index, :create] + resources :applied_transfer_projects, only: [:create] do + collection do + get :organizations + post :cancel + end + end scope do get( '/blob/*id/diff', @@ -592,6 +606,10 @@ Rails.application.routes.draw do end end # Project Area END + + scope module: :helps do + resources :faqs, only: [:index] + end end namespace :admins do @@ -768,6 +786,7 @@ Rails.application.routes.draw do post :drag, on: :collection post :replace_image_url, on: :member end + resources :faqs resources :laboratories, only: [:index, :create, :destroy, :update] do member do get :shixuns_for_select diff --git a/db/migrate/20210408070123_add_lesson_url_to_projects.rb b/db/migrate/20210408070123_add_lesson_url_to_projects.rb new file mode 100644 index 00000000..14938348 --- /dev/null +++ b/db/migrate/20210408070123_add_lesson_url_to_projects.rb @@ -0,0 +1,5 @@ +class AddLessonUrlToProjects < ActiveRecord::Migration[5.2] + def change + add_column :projects, :lesson_url, :string + end +end diff --git a/db/migrate/20210419023310_add_nickname_to_teams.rb b/db/migrate/20210419023310_add_nickname_to_teams.rb new file mode 100644 index 00000000..ed80f193 --- /dev/null +++ b/db/migrate/20210419023310_add_nickname_to_teams.rb @@ -0,0 +1,5 @@ +class AddNicknameToTeams < ActiveRecord::Migration[5.2] + def change + add_column :teams, :nickname, :string + end +end diff --git a/db/migrate/20210425032825_create_applied_transfer_projects.rb b/db/migrate/20210425032825_create_applied_transfer_projects.rb new file mode 100644 index 00000000..5c677409 --- /dev/null +++ b/db/migrate/20210425032825_create_applied_transfer_projects.rb @@ -0,0 +1,12 @@ +class CreateAppliedTransferProjects < ActiveRecord::Migration[5.2] + def change + create_table :applied_transfer_projects do |t| + t.references :project + t.references :owner + t.references :user + t.integer :status, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20210429095016_add_accelerator_url_to_repositories.rb b/db/migrate/20210429095016_add_accelerator_url_to_repositories.rb new file mode 100644 index 00000000..0c33ef7b --- /dev/null +++ b/db/migrate/20210429095016_add_accelerator_url_to_repositories.rb @@ -0,0 +1,5 @@ +class AddAcceleratorUrlToRepositories < ActiveRecord::Migration[5.2] + def change + add_column :repositories, :accelerator_url, :string, default: "" + end +end diff --git a/db/migrate/20210429100619_add_source_clone_url_to_repositories.rb b/db/migrate/20210429100619_add_source_clone_url_to_repositories.rb new file mode 100644 index 00000000..a7f21303 --- /dev/null +++ b/db/migrate/20210429100619_add_source_clone_url_to_repositories.rb @@ -0,0 +1,5 @@ +class AddSourceCloneUrlToRepositories < ActiveRecord::Migration[5.2] + def change + add_column :repositories, :source_clone_url, :string, default: "" + end +end diff --git a/db/migrate/20210514024444_create_faqs.rb b/db/migrate/20210514024444_create_faqs.rb new file mode 100644 index 00000000..d63a64fa --- /dev/null +++ b/db/migrate/20210514024444_create_faqs.rb @@ -0,0 +1,10 @@ +class CreateFaqs < ActiveRecord::Migration[5.2] + def change + create_table :faqs do |t| + t.string :question + t.string :url + + t.timestamps + end + end +end diff --git a/lib/tasks/sync_org_project_permission.rake b/lib/tasks/sync_org_project_permission.rake new file mode 100644 index 00000000..5828d1c8 --- /dev/null +++ b/lib/tasks/sync_org_project_permission.rake @@ -0,0 +1,8 @@ +namespace :sync_org_project_permission do + desc "sync organization project team permissions" + task mirror: :environment do + Project.mirror.includes(:team_projects,:owner).where(team_projects: {id: nil}, users: {type: 'Organization'}).find_each do |project| + project.set_owner_permission(nil) + end + end +end \ No newline at end of file diff --git a/public/docs/api.html b/public/docs/api.html index 1b75a97a..f970ea84 100644 --- a/public/docs/api.html +++ b/public/docs/api.html @@ -331,6 +331,18 @@
  • 获取当前登陆用户信息
  • +
  • + 待办事项-用户通知信息 +
  • +
  • + 待办事项-接受仓库 +
  • +
  • + 用户接受迁移 +
  • +
  • + 用户拒绝迁移 +
  • @@ -366,6 +378,15 @@
  • Fork项目
  • +
  • + 用户管理的组织列表 +
  • +
  • + 迁移项目 +
  • +
  • + 取消迁移项目 +
  • @@ -563,6 +584,12 @@ http://localhost:3000/api/ignores.json +

    Users

    获取当前登陆用户信息

    获取当前登陆用户信息

    @@ -622,7 +649,818 @@ Success — a happy kitten is an authenticated kitten! -

    Projects

    获取项目列表

    +

    待办事项-用户通知信息

    +

    待办事项-用户通知信息

    + +
    +

    示例:

    +
    +
    curl -X GET http://localhost:3000/api/users/yystopf/applied_messages.json
    +
    await octokit.request('GET /api/users/:login/applied_messages.json')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_messages.json

    +

    请求字段说明:

    + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    appliedobject通知主体
    applied.idint通知主体的迁移id
    applied.statusstring通知主体的迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    applied.time_agostring通知主体的迁移创建的时间
    applied.project.idint通知主体的迁移项目的id
    applied.project.identifierstring通知主体的迁移项目的标识
    applied.project.namestring通知主体的迁移项目的名称
    applied.project.descriptionstring通知主体的迁移项目的描述
    applied.project.is_publicbool通知主体的迁移项目是否公开
    applied.project.owner.idbool通知主体的迁移项目拥有者id
    applied.project.owner.typestring通知主体的迁移项目拥有者类型
    applied.project.owner.namestring通知主体的迁移项目拥有者昵称
    applied.project.owner.loginstring通知主体的迁移项目拥有者标识
    applied.project.owner.image_urlstring通知主体的迁移项目拥有者头像
    applied.user.idint通知主体的迁移创建者的id
    applied.user.typestring通知主体的迁移创建者的类型
    applied.user.namestring通知主体的迁移创建者的名称
    applied.user.loginstring通知主体的迁移创建者的标识
    applied.user.image_urlstring通知主体的迁移创建者头像
    applied.owner.idint通知主体的迁移接受者的id
    applied.owner.typestring通知主体的迁移接受者的类型
    applied.owner.namestring通知主体的迁移接受者的名称
    applied.owner.loginstring通知主体的迁移接受者的标识
    applied.owner.image_urlstring通知主体的迁移接受者头像
    applied_typestring通知类型
    namestring通知内容
    viewedstring是否已读,waiting:未读,viewed:已读
    statusstring通知状态, canceled:已取消,common: 正常,successed:成功,failure:失败
    time_agostring通知时间
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "total_count": 5,
    +    "applied_messages": [
    +        {
    +            "applied": {
    +                "project": {
    +                    "id": 86,
    +                    "identifier": "ceshi_repo1",
    +                    "name": "测试项目啊1",
    +                    "description": "二十多",
    +                    "is_public": true,
    +                    "owner": {
    +                        "id": 52,
    +                        "type": "Organization",
    +                        "name": "身份卡手动阀",
    +                        "login": "ceshi1",
    +                        "image_url": "images/avatars/Organization/52?t=1618805056"
    +                    }
    +                },
    +                "user": {
    +                    "id": 6,
    +                    "type": "User",
    +                    "name": "yystopf",
    +                    "login": "yystopf",
    +                    "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +                },
    +                "owner": {
    +                    "id": 9,
    +                    "type": "Organization",
    +                    "name": "测试组织",
    +                    "login": "ceshi_org",
    +                    "image_url": "images/avatars/Organization/9?t=1612706073"
    +                },
    +                "id": 4,
    +                "status": "common",
    +                "created_at": "2021-04-26 09:54",
    +                "time_ago": "35分钟前"
    +            },
    +            "applied_user": {
    +                "id": 6,
    +                "type": "User",
    +                "name": "yystopf",
    +                "login": "yystopf",
    +                "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +            },
    +            "applied_type": "AppliedTransferProject",
    +            "name": "正在将【测试项目啊1】仓库转移给【测试组织】",
    +            "viewed": "viewed",
    +            "status": "common",
    +            "created_at": "2021-04-26 09:54",
    +            "time_ago": "35分钟前"
    +        },
    +        ...
    +    ]
    +}
    +

    待办事项-接受仓库

    +

    待办事项-接受仓库

    + +
    +

    示例:

    +
    +
    curl -X GET http://localhost:3000/api/users/yystopf/applied_transfer_projects.json
    +
    await octokit.request('GET /api/users/:login/applied_transfer_projects.json')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_transfer_projects.json

    +

    请求字段说明:

    + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "total_count": 4,
    +    "applied_transfer_projects": [
    +        {
    +            "project": {
    +                "id": 86,
    +                "identifier": "ceshi_repo1",
    +                "name": "测试项目啊1",
    +                "description": "二十多",
    +                "is_public": true,
    +                "owner": {
    +                    "id": 52,
    +                    "type": "Organization",
    +                    "name": "身份卡手动阀",
    +                    "login": "ceshi1",
    +                    "image_url": "images/avatars/Organization/52?t=1618805056"
    +                }
    +            },
    +            "user": {
    +                "id": 6,
    +                "type": "User",
    +                "name": "yystopf",
    +                "login": "yystopf",
    +                "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +            },
    +            "owner": {
    +                "id": 52,
    +                "type": "Organization",
    +                "name": "身份卡手动阀",
    +                "login": "ceshi1",
    +                "image_url": "images/avatars/Organization/52?t=1618805056"
    +            },
    +            "id": 1,
    +            "status": "canceled",
    +            "created_at": "2021-04-25 18:06",
    +            "time_ago": "16小时前"
    +        },
    +        ...
    +    ]
    +}
    +

    用户接受迁移

    +

    用户接受迁移

    + +
    +

    示例:

    +
    +
    curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/accept.json
    +
    await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/accept.json')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_transfer_projects/:id/accept.json

    +

    请求字段说明:

    + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    idint迁移id
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "project": {
    +        "id": 86,
    +        "identifier": "ceshi_repo1",
    +        "name": "测试项目啊1",
    +        "description": "二十多",
    +        "is_public": true,
    +        "owner": {
    +            "id": 52,
    +            "type": "Organization",
    +            "name": "身份卡手动阀",
    +            "login": "ceshi1",
    +            "image_url": "images/avatars/Organization/52?t=1618805056"
    +        }
    +    },
    +    "user": {
    +        "id": 6,
    +        "type": "User",
    +        "name": "yystopf",
    +        "login": "yystopf",
    +        "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +    },
    +    "owner": {
    +        "id": 52,
    +        "type": "Organization",
    +        "name": "身份卡手动阀",
    +        "login": "ceshi1",
    +        "image_url": "images/avatars/Organization/52?t=1618805056"
    +    },
    +    "id": 1,
    +    "status": "canceled",
    +    "created_at": "2021-04-25 18:06",
    +    "time_ago": "16小时前"
    +}
    +

    用户拒绝迁移

    +

    用户拒绝迁移

    + +
    +

    示例:

    +
    +
    curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/refuse.json
    +
    await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/refuse.json')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_transfer_projects/:id/refuse.json

    +

    请求字段说明:

    + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    idint迁移id
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "project": {
    +        "id": 86,
    +        "identifier": "ceshi_repo1",
    +        "name": "测试项目啊1",
    +        "description": "二十多",
    +        "is_public": true,
    +        "owner": {
    +            "id": 52,
    +            "type": "Organization",
    +            "name": "身份卡手动阀",
    +            "login": "ceshi1",
    +            "image_url": "images/avatars/Organization/52?t=1618805056"
    +        }
    +    },
    +    "user": {
    +        "id": 6,
    +        "type": "User",
    +        "name": "yystopf",
    +        "login": "yystopf",
    +        "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +    },
    +    "owner": {
    +        "id": 52,
    +        "type": "Organization",
    +        "name": "身份卡手动阀",
    +        "login": "ceshi1",
    +        "image_url": "images/avatars/Organization/52?t=1618805056"
    +    },
    +    "id": 1,
    +    "status": "canceled",
    +    "created_at": "2021-04-25 18:06",
    +    "time_ago": "16小时前"
    +}
    +

    Projects

    获取项目列表

    获取项目列表,也可以更加相关条件过滤搜素

    @@ -1590,6 +2428,502 @@ http://localhost:3000/api/projects/migrate.json "id": 3290, "identifier": "newadm" } +

    用户管理的组织列表

    +

    用户管理的组织列表

    + +
    +

    示例:

    +
    +
    curl -X GET \
    +http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizations.json  | jq
    +
    await octokit.request('GET /api/:owner/:repo/applied_transfer_projects/organizations')
    +

    HTTP 请求

    +

    GET api/:owner/:repo/applied_transfer_projects/organizations

    +

    请求参数

    + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    +

    返回字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    namestring组织标识
    nicknamestring组织名称
    descriptionstring组织描述
    avatar_urlstring|组织头像
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "total_count": 3,
    +    "organizations": [
    +        {
    +            "id": 9,
    +            "name": "ceshi_org",
    +            "nickname": "测试组织",
    +            "description": "测试组织",
    +            "avatar_url": "images/avatars/Organization/9?t=1612706073"
    +        },
    +        {
    +            "id": 51,
    +            "name": "ceshi",
    +            "nickname": "测试组织哈哈哈",
    +            "description": "23212312",
    +            "avatar_url": "images/avatars/Organization/51?t=1618800723"
    +        },
    +        {
    +            "id": 52,
    +            "name": "ceshi1",
    +            "nickname": "身份卡手动阀",
    +            "description": "1231手动阀是的",
    +            "avatar_url": "images/avatars/Organization/52?t=1618805056"
    +        }
    +    ]
    +}
    +

    迁移项目

    +

    迁移项目,edit接口is_transfering为true表示正在迁移

    + +
    +

    示例:

    +
    +
    curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects.json
    +
    await octokit.request('POST /api/:owner/:repo/applied_transfer_projects.json')
    +

    HTTP 请求

    +

    POST /api/:owner/:repo/applied_transfer_projects.json

    +

    请求参数

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    owner_namestring迁移对象标识
    +

    返回字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint项目id
    statusstring项目迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring项目迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "project": {
    +        "id": 86,
    +        "identifier": "ceshi_repo1",
    +        "name": "测试项目啊1",
    +        "description": "二十多",
    +        "is_public": true,
    +        "owner": {
    +            "id": 52,
    +            "type": "Organization",
    +            "name": "身份卡手动阀",
    +            "login": "ceshi1",
    +            "image_url": "images/avatars/Organization/52?t=1618805056"
    +        }
    +    },
    +    "user": {
    +        "id": 6,
    +        "type": "User",
    +        "name": "yystopf",
    +        "login": "yystopf",
    +        "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +    },
    +    "owner": {
    +        "id": 9,
    +        "type": "Organization",
    +        "name": "测试组织",
    +        "login": "ceshi_org",
    +        "image_url": "images/avatars/Organization/9?t=1612706073"
    +    },
    +    "id": 4,
    +    "status": "common",
    +    "created_at": "2021-04-26 09:54",
    +    "time_ago": "1分钟前"
    +}
    +

    取消迁移项目

    +

    迁移项目,edit接口is_transfering为true表示正在迁移

    + +
    +

    示例:

    +
    +
    curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/cancel.json
    +
    await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.json')
    +

    HTTP 请求

    +

    POST /api/:owner/:repo/applied_transfer_projects/cancel.json

    +

    请求参数

    + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    +

    返回字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的JSON示例:

    +
    +
    {
    +    "project": {
    +        "id": 86,
    +        "identifier": "ceshi_repo1",
    +        "name": "测试项目啊1",
    +        "description": "二十多",
    +        "is_public": true,
    +        "owner": {
    +            "id": 52,
    +            "type": "Organization",
    +            "name": "身份卡手动阀",
    +            "login": "ceshi1",
    +            "image_url": "images/avatars/Organization/52?t=1618805056"
    +        }
    +    },
    +    "user": {
    +        "id": 6,
    +        "type": "User",
    +        "name": "yystopf",
    +        "login": "yystopf",
    +        "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
    +    },
    +    "owner": {
    +        "id": 9,
    +        "type": "Organization",
    +        "name": "测试组织",
    +        "login": "ceshi_org",
    +        "image_url": "images/avatars/Organization/9?t=1612706073"
    +    },
    +    "id": 4,
    +    "status": "common",
    +    "created_at": "2021-04-26 09:54",
    +    "time_ago": "1分钟前"
    +}
     

    Repositories

    仓库详情

    仓库详情