Browse Source

ADD merge develop branch

pull/141/head
jasder 5 years ago
parent
commit
5a8dc9d5e7
100 changed files with 3331 additions and 375 deletions
  1. +16
    -1
      CHANGELOG.md
  2. +3
    -1
      Gemfile.lock
  3. +124
    -21
      LICENSE
  4. +19
    -0
      app/controllers/accounts_controller.rb
  5. +1
    -1
      app/controllers/admins/auth_schools_controller.rb
  6. +2
    -2
      app/controllers/admins/laboratories_controller.rb
  7. +19
    -0
      app/controllers/application_controller.rb
  8. +13
    -0
      app/controllers/applied_projects_controller.rb
  9. +1
    -1
      app/controllers/members_controller.rb
  10. +1
    -1
      app/controllers/organizations/organization_users_controller.rb
  11. +0
    -19
      app/controllers/organizations/organizations_controller.rb
  12. +1
    -1
      app/controllers/projects_controller.rb
  13. +1
    -1
      app/controllers/users/applied_messages_controller.rb
  14. +39
    -0
      app/controllers/users/applied_projects_controller.rb
  15. +1
    -1
      app/controllers/users/applied_transfer_projects_controller.rb
  16. +26
    -0
      app/controllers/users/headmaps_controller.rb
  17. +45
    -0
      app/controllers/users/is_pinned_projects_controller.rb
  18. +11
    -0
      app/controllers/users/project_trends_controller.rb
  19. +217
    -0
      app/controllers/users/statistics_controller.rb
  20. +17
    -8
      app/controllers/users_controller.rb
  21. +1
    -1
      app/controllers/users_for_private_messages_controller.rb
  22. +2
    -2
      app/controllers/weapps/courses_controller.rb
  23. +2
    -2
      app/controllers/zips_controller.rb
  24. +86
    -0
      app/docs/slate/source/includes/_projects.md
  25. +1196
    -6
      app/docs/slate/source/includes/_users.md
  26. +10
    -0
      app/helpers/application_helper.rb
  27. +3
    -3
      app/helpers/tag_chosen_helper.rb
  28. +16
    -1
      app/interactors/projects/add_member_interactor.rb
  29. +14
    -0
      app/jobs/reset_platform_cache_job.rb
  30. +14
    -0
      app/jobs/reset_user_cache_job.rb
  31. +27
    -0
      app/jobs/send_join_project_applied_message_job.rb
  32. +21
    -1
      app/jobs/sync_mirrored_repository_job.rb
  33. +8
    -0
      app/models/application_record.rb
  34. +6
    -2
      app/models/applied_project.rb
  35. +6
    -5
      app/models/attachment.rb
  36. +9
    -8
      app/models/ci/user.rb
  37. +28
    -0
      app/models/concerns/dcodes.rb
  38. +3
    -2
      app/models/concerns/project_operable.rb
  39. +8
    -0
      app/models/fork_user.rb
  40. +8
    -3
      app/models/issue.rb
  41. +0
    -1
      app/models/laboratory.rb
  42. +1
    -0
      app/models/license.rb
  43. +1
    -0
      app/models/member.rb
  44. +17
    -8
      app/models/organization.rb
  45. +22
    -0
      app/models/pinned_project.rb
  46. +10
    -0
      app/models/praise_tread.rb
  47. +95
    -75
      app/models/project.rb
  48. +5
    -0
      app/models/project_category.rb
  49. +11
    -1
      app/models/pull_request.rb
  50. +4
    -0
      app/models/repository.rb
  51. +31
    -9
      app/models/user.rb
  52. +1
    -3
      app/models/user_action.rb
  53. +1
    -4
      app/models/user_agent.rb
  54. +28
    -25
      app/models/user_extension.rb
  55. +2
    -1
      app/models/version.rb
  56. +21
    -7
      app/models/watcher.rb
  57. +1
    -1
      app/queries/admins/apply_item_bank_query.rb
  58. +1
    -1
      app/queries/admins/apply_user_authentication_query.rb
  59. +1
    -1
      app/queries/admins/course_list_query.rb
  60. +1
    -1
      app/queries/admins/course_query.rb
  61. +1
    -1
      app/queries/admins/laboratory_shixun_query.rb
  62. +1
    -1
      app/queries/admins/laboratory_subject_query.rb
  63. +1
    -1
      app/queries/admins/subject_query.rb
  64. +2
    -2
      app/queries/admins/user_query.rb
  65. +7
    -2
      app/queries/projects/list_my_query.rb
  66. +1
    -1
      app/queries/user_query.rb
  67. +1
    -1
      app/services/admins/import_user_service.rb
  68. +41
    -0
      app/services/cache/platform_follow_count_service.rb
  69. +41
    -0
      app/services/cache/platform_issue_count_service.rb
  70. +41
    -0
      app/services/cache/platform_project_count_service.rb
  71. +41
    -0
      app/services/cache/platform_project_fork_count_service.rb
  72. +57
    -0
      app/services/cache/platform_project_languages_count_service.rb
  73. +41
    -0
      app/services/cache/platform_project_praises_count_service.rb
  74. +41
    -0
      app/services/cache/platform_project_watchers_count_service.rb
  75. +41
    -0
      app/services/cache/platform_pullrequest_count_service.rb
  76. +43
    -0
      app/services/cache/user_follow_count_service.rb
  77. +43
    -0
      app/services/cache/user_issue_count_service.rb
  78. +43
    -0
      app/services/cache/user_project_count_service.rb
  79. +43
    -0
      app/services/cache/user_project_fork_count_service.rb
  80. +60
    -0
      app/services/cache/user_project_languages_count_service.rb
  81. +43
    -0
      app/services/cache/user_project_praises_count_service.rb
  82. +43
    -0
      app/services/cache/user_project_watchers_count_service.rb
  83. +43
    -0
      app/services/cache/user_pullrequest_count_service.rb
  84. +96
    -0
      app/services/gitea/accelerator/base_service.rb
  85. +2
    -95
      app/services/gitea/accelerator/migrate_service.rb
  86. +31
    -0
      app/services/gitea/accelerator/sync_mirrored_service.rb
  87. +23
    -0
      app/services/gitea/user/headmap_service.rb
  88. +61
    -0
      app/services/projects/accept_join_service.rb
  89. +22
    -16
      app/services/projects/apply_join_service.rb
  90. +39
    -0
      app/services/projects/refuse_join_service.rb
  91. +1
    -1
      app/services/projects/transfer_service.rb
  92. +4
    -1
      app/views/admins/shared/_sidebar.html.erb
  93. +19
    -0
      app/views/applied_projects/_detail.json.jbuilder
  94. +1
    -0
      app/views/applied_projects/create.json.jbuilder
  95. +6
    -0
      app/views/issues/_simple_issue_item.json.jbuilder
  96. +17
    -0
      app/views/project_trends/_detail.json.jbuilder
  97. +2
    -18
      app/views/project_trends/index.json.jbuilder
  98. +4
    -0
      app/views/projects/_project_detail.json.jbuilder
  99. +3
    -2
      app/views/repositories/detail.json.jbuilder
  100. +2
    -1
      app/views/repositories/edit.json.jbuilder

+ 16
- 1
CHANGELOG.md View File

@@ -1,6 +1,21 @@
# Changelog
## [v3.1.0](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-06-09

* ENHANCEMENTS
* ADD 用户活动统计图表功能
* ADD 用户精选项目功能
* ADD 用户贡献度统计图表功能
* ADD 用户开发能力数据统计工
* ADD 用户角色定位展示功能
* ADD 用户专业定位标签展示功能
* ADD 修改用户基本资料功能
* ADD 更改密码功能
* ADD 用户个人主页基本现在展示可配置功能

* BUGFIXES
* Fix 解决一些bug
* Fix 优化美化页面

## [v3.0.4](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-24

* BUGFIXES
* Fix 在线修改文件,页面文件显不及时的问题(46049)


+ 3
- 1
Gemfile.lock View File

@@ -176,7 +176,9 @@ GEM
mimemagic (~> 0.3.2)
maruku (0.7.3)
method_source (0.9.2)
mimemagic (0.3.4)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.0)


+ 124
- 21
LICENSE View File

@@ -1,21 +1,124 @@
MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
木兰宽松许可证, 第2版

2020年1月 http://license.coscl.org.cn/MulanPSL2

您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:

0. 定义

“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。

“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。

“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。

“法人实体” 是指提交贡献的机构及其“关联实体”。

“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。

1. 授予版权许可

每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。

2. 授予专利许可

每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。

3. 无商标许可

“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。

4. 分发限制

您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。

5. 免责声明与责任限制

“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。

6. 语言

“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。

条款结束

如何将木兰宽松许可证,第2版,应用到您的软件

如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:

1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;

2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;

3, 请将如下声明文本放入每个源文件的头部注释中。

Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
Mulan Permissive Software License,Version 2
Mulan Permissive Software License,Version 2 (Mulan PSL v2)

January 2020 http://license.coscl.org.cn/MulanPSL2

Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:

0. Definition

Software means the program and related documents which are licensed under this License and comprise all Contribution(s).

Contribution means the copyrightable work licensed by a particular Contributor under this License.

Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.

Legal Entity means the entity making a Contribution and all its Affiliates.

Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.

1. Grant of Copyright License

Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.

2. Grant of Patent License

Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.

3. No Trademark License

No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4.

4. Distribution Restriction

You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.

5. Disclaimer of Warranty and Limitation of Liability

THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

6. Language

THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.

END OF THE TERMS AND CONDITIONS

How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software

To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:

Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
Create a file named "LICENSE" which contains the whole context of this License in the first directory of your software package;
Attach the statement to the appropriate annotated syntax at the beginning of each source file.
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.

+ 19
- 0
app/controllers/accounts_controller.rb View File

@@ -210,6 +210,25 @@ class AccountsController < ApplicationController
# session[:user_id] = @user.id
end
def change_password
@user = User.find_by(login: params[:login])
return render_error("未找到相关用户!") if @user.blank?
return render_error("旧密码不正确") unless @user.check_password?(params[:old_password])
sync_params = {
password: params[:password].to_s,
email: @user.mail
}
interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params)
if interactor.success?
@user.update_attribute(:password, params[:password])
render_ok
else
render_error(interactor.error)
end
end
# 忘记密码
def reset_password
begin


+ 1
- 1
app/controllers/admins/auth_schools_controller.rb View File

@@ -32,7 +32,7 @@ class Admins::AuthSchoolsController < Admins::BaseController
def search_manager
school = School.find_by(id: params[:school_id])
user_ids = school&.ec_school_users&.pluck(:user_id)
@users = User.where.not(id: user_ids).where("concat(lastname, firstname) like ?", "%#{params[:name].strip.to_s}%").limit(10)
@users = User.where.not(id: user_ids).where("CONCAT_WS(lastname, firstname, nickname) like ?", "%#{params[:name].strip.to_s}%").limit(10)
end

# 添加认证学校管理员


+ 2
- 2
app/controllers/admins/laboratories_controller.rb View File

@@ -32,7 +32,7 @@ class Admins::LaboratoriesController < Admins::BaseController

keyword = params[:keyword].to_s.strip
if keyword.present?
like_sql = 'shixuns.name LIKE :keyword OR CONCAT(users.lastname, users.firstname) LIKE :keyword '\
like_sql = 'shixuns.name LIKE :keyword OR CONCAT_WS(users.lastname, users.firstname, users.nickname) LIKE :keyword '\
'OR mirror_repositories.name LIKE :keyword'
shixuns = shixuns.joins(:user, :mirror_repositories).where(like_sql, keyword: "%#{keyword}%")
end
@@ -48,7 +48,7 @@ class Admins::LaboratoriesController < Admins::BaseController

keyword = params[:keyword].to_s.strip
if keyword.present?
like_sql = 'subjects.name LIKE :keyword OR CONCAT(users.lastname, users.firstname) LIKE :keyword'
like_sql = 'subjects.name LIKE :keyword OR CONCAT_WS(users.lastname, users.firstname, users.nickname) LIKE :keyword'
subjects = subjects.joins(:user).where(like_sql, keyword: "%#{keyword}%")
end



+ 19
- 0
app/controllers/application_controller.rb View File

@@ -773,7 +773,26 @@ class ApplicationController < ActionController::Base
def base_url
request.base_url
end

def convert_image!
@image = params[:image] || user_params[:image]
return unless @image.present?
max_size = EduSetting.get('upload_avatar_max_size') || 2 * 1024 * 1024 # 2M
if @image.class == ActionDispatch::Http::UploadedFile
render_error('请上传文件') if @image.size.zero?
render_error('文件大小超过限制') if @image.size > max_size.to_i
else
image = @image.to_s.strip
return render_error('请上传正确的图片') if image.blank?
@image = Util.convert_base64_image(image, max_size: max_size.to_i)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
def avatar_path(object)
ApplicationController.helpers.disk_filename(object.class, object.id)
end

private
def object_not_found


+ 13
- 0
app/controllers/applied_projects_controller.rb View File

@@ -0,0 +1,13 @@
class AppliedProjectsController < ApplicationController
before_action :require_login
def create
@applied_project = Projects::ApplyJoinService.call(current_user, applied_params)
rescue Projects::ApplyJoinService::Error => ex
render_error(ex.message)
end

private
def applied_params
params.require(:applied_project).permit(:code, :role)
end
end

+ 1
- 1
app/controllers/members_controller.rb View File

@@ -27,7 +27,7 @@ class MembersController < ApplicationController
scope = @project.members.includes(:roles, user: :user_extension)
search = params[:search].to_s.downcase
role = params[:role].to_s
scope = scope.joins(:user).where("LOWER(concat(users.lastname, users.firstname, users.login, users.mail)) LIKE ?", "%#{search.split(" ").join('|')}%") if search.present?
scope = scope.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.login, users.mail, users.nickname)) LIKE ?", "%#{search.split(" ").join('|')}%") if search.present?
scope = scope.joins(:roles).where("roles.name LIKE ?", "%#{role}%") if role.present?

@total_count = scope.size


+ 1
- 1
app/controllers/organizations/organization_users_controller.rb View File

@@ -5,7 +5,7 @@ class Organizations::OrganizationUsersController < Organizations::BaseController
def index
@organization_users = @organization.organization_users.includes(:user)
search = params[:search].to_s.downcase
@organization_users = @organization_users.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.login, users.mail)) LIKE ?", "%#{search.split(" ").join('|')}%") if search.present?
@organization_users = @organization_users.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.login, users.mail, users.nickname)) LIKE ?", "%#{search.split(" ").join('|')}%") if search.present?

@organization_users = kaminari_paginate(@organization_users)
end


+ 0
- 19
app/controllers/organizations/organizations_controller.rb View File

@@ -70,25 +70,6 @@ class Organizations::OrganizationsController < Organizations::BaseController
end

private
def convert_image!
return unless params[:image].present?
max_size = EduSetting.get('upload_avatar_max_size') || 2 * 1024 * 1024 # 2M
if params[:image].class == ActionDispatch::Http::UploadedFile
@image = params[:image]
render_error('请上传文件') if @image.size.zero?
render_error('文件大小超过限制') if @image.size > max_size.to_i
else
image = params[:image].to_s.strip
return render_error('请上传正确的图片') if image.blank?
@image = Util.convert_base64_image(image, max_size: max_size.to_i)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end

def avatar_path(organization)
ApplicationController.helpers.disk_filename(organization.class, organization.id)
end

def organization_params
params.permit(:name, :description, :website, :location,


+ 1
- 1
app/controllers/projects_controller.rb View File

@@ -184,7 +184,7 @@ class ProjectsController < ApplicationController
end

def recommend
@projects = Project.recommend.includes(:repository, :project_category, :owner).order(id: :desc)
@projects = Project.recommend.includes(:repository, :project_category, :owner).order(visits: :desc)
end

def about


+ 1
- 1
app/controllers/users/applied_messages_controller.rb View File

@@ -9,7 +9,7 @@ class Users::AppliedMessagesController < Users::BaseController

private
def check_auth
return render_forbidden unless observed_logged_user?
return render_forbidden unless current_user.admin? || observed_logged_user?
end

def view_messages


+ 39
- 0
app/controllers/users/applied_projects_controller.rb View File

@@ -0,0 +1,39 @@
class Users::AppliedProjectsController < Users::BaseController
before_action :check_auth
before_action :find_applied_project, except: [:index]
before_action :find_project, except: [:index]

def index
@applied_projects = AppliedProject.where(project_id: observed_user.full_admin_projects)
@applied_projects = paginate @applied_projects.order("created_at desc")
end

# 接受申请
def accept
@applied_project = Projects::AcceptJoinService.call(current_user, @applied_project)
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end

# 拒绝申请
def refuse
@applied_project = Projects::RefuseJoinService.call(current_user, @applied_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? || observed_logged_user?
end

def find_applied_project
@applied_project = AppliedProject.find_by_id params[:id]
end

def find_project
@project = @applied_project.project
end
end

+ 1
- 1
app/controllers/users/applied_transfer_projects_controller.rb View File

@@ -28,7 +28,7 @@ class Users::AppliedTransferProjectsController < Users::BaseController

private
def check_auth
return render_forbidden unless observed_logged_user?
return render_forbidden unless current_user.admin? || observed_logged_user?
end

def find_applied_transfer_project


+ 26
- 0
app/controllers/users/headmaps_controller.rb View File

@@ -0,0 +1,26 @@
class Users::HeadmapsController < Users::BaseController
def index
result = Gitea::User::HeadmapService.call(observed_user.login, start_stamp, end_stamp)
@headmaps = result[2]
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end

private
def start_stamp
if params[:year].present?
Date.new(params[:year], 1).to_time.to_i
else
Date.today.to_time.to_i - 365*24*60*60
end
end

def end_stamp
if params[:year].present?
Date.new(params[:year], 1).to_time.to_i + 365*24*60*60
else
Date.today.to_time.to_i
end
end
end

+ 45
- 0
app/controllers/users/is_pinned_projects_controller.rb View File

@@ -0,0 +1,45 @@
class Users::IsPinnedProjectsController < Users::BaseController
before_action :private_user_resources!, only: [:pin]
def index
@is_pinned_projects = observed_user.pinned_projects.order(position: :desc, created_at: :asc).includes(project: [:project_category, :project_language, :repository]).order(position: :desc)
@is_pinned_projects = kaminari_paginate(@is_pinned_projects)
end
def pin
observed_user.is_pinned_project_ids = is_pinned_project_ids
render_ok
rescue ActiveRecord::RecordNotFound => e
render_not_found
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end

def update
@pinned_project = PinnedProject.find_by_id(params[:id])
@pinned_project.attributes = pinned_project_params
if @pinned_project.save
render_ok
else
render_error
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end

private
def is_pinned_project_ids
if params[:is_pinned_project_ids].present?
return params[:is_pinned_project_ids].select{|id| observed_user.full_member_projects.visible.pluck(:id).include?(id.to_i) }
end
if params[:is_pinned_project_id].present?
return observed_user.is_pinned_project_ids unless observed_user.full_member_projects.visible.pluck(:id).include?(params[:is_pinned_project_id].to_i)
return observed_user.is_pinned_project_ids.include?(params[:is_pinned_project_id].to_i) ? observed_user.is_pinned_project_ids : observed_user.is_pinned_project_ids.push(params[:is_pinned_project_id].to_i)
end
end
def pinned_project_params
params.require(:pinned_project).permit(:position)
end
end

+ 11
- 0
app/controllers/users/project_trends_controller.rb View File

@@ -0,0 +1,11 @@
class Users::ProjectTrendsController < Users::BaseController

def index
if params[:date].present?
@project_trends = observed_user.project_trends.where("DATE(created_at) = ?", params[:date])
else
@project_trends = observed_user.project_trends
end
@project_trends = kaminari_paginate(@project_trends.includes(:trend, :project).order(created_at: :desc))
end
end

+ 217
- 0
app/controllers/users/statistics_controller.rb View File

@@ -0,0 +1,217 @@
class Users::StatisticsController < Users::BaseController
before_action :preload_develop_data, only: [:develop]

# 近期活动统计
def activity
date_range = (1.week.ago.to_date..Date.today).to_a
commit_request = Gitea::User::HeadmapService.call(observed_user.login, 1.week.ago.to_date.to_time.to_i, Date.today.to_time.to_i)
commit_data = commit_request[2]
@date_data = []
@issue_data = []
@pull_request_data = []
@commit_data = []
date_range.each do |date|
@date_data << date.strftime("%Y.%m.%d")
@issue_data << observed_user.issues.where("DATE(created_on) = ?", date).size
@pull_request_data << observed_user.pull_requests.where("DATE(created_at) = ?", date).size
date_commit_data = commit_data.select{|item| item["timestamp"] == date.to_time.to_i}
@commit_data << (date_commit_data.blank? ? 0 : date_commit_data[0]["contributions"].to_i)
end
render :json => {dates: @date_data, issues_count: @issue_data, pull_requests_count: @pull_request_data, commits_count: @commit_data}
end

# 开发能力
def develop
if params[:start_time].present? && params[:end_time].present?
# 影响力
@influence = (60.0 + @follow_count / (@follow_count + 10.0) * 40.0).to_i
@platform_influence = (60.0 + @platform_follow_count / (@platform_follow_count + 10.0) * 40.0).to_i
# 贡献度
@contribution = (60.0 + @pullrequest_count / (@pullrequest_count + 10.0) * 40.0).to_i
@platform_contribution = (60.0 + @platform_pullrequest_count / (@platform_pullrequest_count + 10.0) * 40.0).to_i
# 活跃度
@activity = (60.0 + @issues_count / (@issues_count + 10.0) * 40.0).to_i
@platform_activity = (60.0 + @platform_issues_count / (@platform_issues_count + 10.0) * 40.0).to_i
# 项目经验
@experience = 10 * @project_count + 5 * @fork_count + @project_watchers_count + @project_praises_count
@experience = (60.0 + @experience / (@experience + 100.0) * 40.0).to_i
@platform_experience = 10 * @platform_project_count + 5 * @platform_fork_count + @platform_project_watchers_count + @platform_project_praises_count
@platform_experience = (60.0 + @platform_experience / (@platform_experience + 100.0) * 40.0).to_i
# 语言能力
@language = (60.0 + @project_languages_count.length / (@project_languages_count.length + 5.0) * 40.0).to_i
@platform_language = (60.0 + @platform_project_languages_count.length / (@platform_project_languages_count.length + 5.0) * 40.0).to_i
# 语言百分比
@languages_percent = Hash.new
for key in @project_languages_count.keys do
@languages_percent[key] = (@project_languages_count[key].to_f / @project_languages_count.values.sum).round(2)
end
@platform_languages_percent = Hash.new
for key in @platform_project_languages_count.keys do
@platform_languages_percent[key] = (@platform_project_languages_count[key].to_f / @platform_project_languages_count.values.sum).round(2)
end
# 各门语言分数
@each_language_score = Hash.new
for key in @project_languages_count.keys do
@each_language_score[key] = (60.0 + @project_languages_count[key] / (@project_languages_count[key] + 5.0) * 40.0).to_i
end
@platform_each_language_score = Hash.new
for key in @platform_project_languages_count.keys do
@platform_each_language_score[key] = (60.0 + @platform_project_languages_count[key] / (@platform_project_languages_count[key] + 5.0) * 40.0).to_i
end
else
# 影响力
@influence = (60.0 + @follow_count / (@follow_count + 20.0) * 40.0).to_i
@platform_influence = (60.0 + @platform_follow_count / (@platform_follow_count + 20.0) * 40.0).to_i

# 贡献度
@contribution = (60.0 + @pullrequest_count / (@pullrequest_count + 20.0) * 40.0).to_i
@platform_contribution = (60.0 + @platform_pullrequest_count / (@platform_pullrequest_count + 20.0) * 40.0).to_i

# 活跃度
@activity = (60.0 + @issues_count / (@issues_count + 80.0) * 40.0).to_i
@platform_activity = (60.0 + @platform_issues_count / (@platform_issues_count + 80.0) * 40.0).to_i

# 项目经验
@experience = 10 * @project_count + 5 * @fork_count + @project_watchers_count + @project_praises_count
@experience = (60.0 + @experience / (@experience + 100.0) * 40.0).to_i
@platform_experience = 10 * @platform_project_count + 5 * @platform_fork_count + @platform_project_watchers_count + @platform_project_praises_count
@platform_experience = (60.0 + @platform_experience / (@platform_experience + 100.0) * 40.0).to_i
# 语言能力
@language = (60.0 + @project_languages_count.length / (@project_languages_count.length + 5.0) * 40.0).to_i
@platform_language = (60.0 + @platform_project_languages_count.length / (@platform_project_languages_count.length + 5.0) * 40.0).to_i

# 语言百分比
@languages_percent = Hash.new
for key in @project_languages_count.keys do
@languages_percent[key] = (@project_languages_count[key].to_f / @project_languages_count.values.sum).round(2)
end
# @platform_languages_percent = Hash.new
# for key in @platform_project_languages_count.keys do
# @platform_languages_percent[key] = (@platform_project_languages_count[key].to_f / @platform_project_languages_count.values.sum).round(2)
# end
# 各门语言分数
@each_language_score = Hash.new
for key in @project_languages_count.keys do
@each_language_score[key] = (60.0 + @project_languages_count[key] / (@project_languages_count[key] + 5.0) * 40.0).to_i
end
# @platform_each_language_score = Hash.new
# for key in @platform_project_languages_count.keys do
# @platform_each_language_score[key] = (60.0 + @platform_project_languages_count[key] / (@platform_project_languages_count[key] + 5.0) * 40.0).to_i
# end
end
end

# 角色定位
def role
full_member_projects = observed_user.full_member_projects
owner_projects = filter_member_projects_by_role("Owner")
manager_projects = filter_member_projects_by_role("Manager").where.not(id: owner_projects.ids)
developer_projects = filter_member_projects_by_role("Developer").where.not(id: owner_projects.ids + manager_projects.ids)
reporter_projects = filter_member_projects_by_role("Reporter").where.not(id: owner_projects.ids + manager_projects.ids + developer_projects.ids)

@full_member_projects_count = full_member_projects.size
@owner_projects_count = owner_projects.size
@manager_projects_count = manager_projects.size
@developer_projects_count = developer_projects.size
@reporter_projects_count = reporter_projects.size

end

# 专业定位
def major
# 参与项目
join_normal_projects_sql = Project.members_projects(observed_user.id).to_sql
join_org_projects_sql = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: observed_user.id}).to_sql
# 关注项目
star_projects_sql = Project.joins(:watchers).where(watchers: {watchable_type: "Project", user_id: observed_user.id}).to_sql
# fork项目
fork_projects_sql = Project.where(id: observed_user.fork_users.select(:id, :fork_project_id).pluck(:fork_project_id)).to_sql
major_projects = Project.from("( #{ join_normal_projects_sql} UNION #{ join_org_projects_sql } UNION #{ star_projects_sql } UNION #{fork_projects_sql}) AS projects").distinct
categories = ProjectCategory.joins(:projects).merge(Project.where(id: time_filter(major_projects, 'created_on'))).distinct.pluck(:name)
render :json => {categories: categories}
end

private
def time_filter(collection, time_field)
if params[:start_time].present? && params[:end_time].present?
return collection.where("#{time_field} > ? AND #{time_field} < ?", Time.at(params[:start_time].to_i).beginning_of_day, Time.at(params[:end_time].to_i).end_of_day)
else
return collection
end
rescue
return collection
end

def filter_member_projects_by_role(role)
case role
when 'Owner'
normal_projects_sql = Project.joins(members: :roles).where(user_id: observed_user.id).where(members: {user_id: observed_user.id}, roles: {name: 'Manager'}).to_sql
org_projects_sql = Project.joins(:owner, teams: :team_users).where(users: {type: 'Organization'}, teams: {authorize: "owner"}, team_users: {user_id: observed_user.id}).to_sql
when "Manager"
normal_projects_sql = Project.joins(members: :roles).where.not(user_id: observed_user.id).where(members: {user_id: observed_user.id}, roles: {name: 'Manager'}).to_sql
org_projects_sql = Project.joins(:owner, teams: :team_users).where(users: {type: 'Organization'}, teams: {authorize: "admin"}, team_users: {user_id: observed_user.id}).to_sql
when "Developer"
normal_projects_sql = Project.joins(members: :roles).where.not(user_id: observed_user.id).where(members: {user_id: observed_user.id}, roles: {name: 'Developer'}).to_sql
org_projects_sql = Project.joins(:owner, teams: :team_users).where(users: {type: 'Organization'}, teams: {authorize: "write"}, team_users: {user_id: observed_user.id}).to_sql
when "Reporter"
normal_projects_sql = Project.joins(members: :roles).where.not(user_id: observed_user.id).where(members: {user_id: observed_user.id}, roles: {name: 'Reporter'}).to_sql
org_projects_sql = Project.joins(:owner, teams: :team_users).where(users: {type: 'Organization'}, teams: {authorize: "read"}, team_users: {user_id: observed_user.id}).to_sql
end
return time_filter(Project.from("( #{normal_projects_sql} UNION #{org_projects_sql} ) AS projects").distinct, 'created_on')
end

def preload_develop_data
if params[:start_time].present? && params[:end_time].present?
# 用户被follow数量
@follow_count = time_filter(Watcher.where(watchable: observed_user), 'created_at').count
@platform_follow_count = time_filter(Watcher.where(watchable_type: 'User'), 'created_at').count
# 用户pr数量
@pullrequest_count = time_filter(PullRequest.where(user_id: observed_user.id), 'created_at').count
@platform_pullrequest_count = time_filter(PullRequest, 'created_at').count
# 用户issue数量
@issues_count = time_filter(Issue.where(author_id: observed_user.id), 'created_on').count
@platform_issues_count = time_filter(Issue, 'created_on').count
# 用户总项目数
@project_count = time_filter(Project.where(user_id:observed_user.id), 'created_on').count
@platform_project_count = time_filter(Project, 'created_on').count
# 用户项目被fork数量
@fork_count = time_filter(Project.where(user_id:observed_user.id), 'created_on').sum("forked_count")
@platform_fork_count = time_filter(Project, 'created_on').sum("forked_count")
# 用户项目关注数
@project_watchers_count = time_filter(Project.where(user_id: observed_user.id), 'created_on').sum("watchers_count")
@platform_project_watchers_count = time_filter(Project, 'created_on').sum("watchers_count")
# 用户项目点赞数
@project_praises_count = time_filter(Project.where(user_id: observed_user.id), 'created_on').sum("praises_count")
@platform_project_praises_count = time_filter(Project, 'created_on').sum("praises_count")
# 用户不同语言项目数量
@project_languages_count = time_filter(Project.where(user_id: observed_user.id), 'created_on').joins(:project_language).group("project_languages.name").count
@platform_project_languages_count = time_filter(Project, 'created_on').joins(:project_language).group("project_languages.name").count
else
# 用户被follow数量
@follow_count = Cache::UserFollowCountService.call(observed_user)
@platform_follow_count = Cache::PlatformFollowCountService.call
# 用户pr数量
@pullrequest_count = Cache::UserPullrequestCountService.call(observed_user)
@platform_pullrequest_count = Cache::PlatformPullrequestCountService.call
# 用户issue数量
@issues_count = Cache::UserIssueCountService.call(observed_user)
@platform_issues_count = Cache::PlatformIssueCountService.call
# 用户总项目数
@project_count = Cache::UserProjectCountService.call(observed_user)
@platform_project_count = Cache::PlatformProjectCountService.call
# 用户项目被fork数量
@fork_count = Cache::UserProjectForkCountService.call(observed_user)
@platform_fork_count = Cache::PlatformProjectForkCountService.call
# 用户项目关注数
@project_watchers_count = Cache::UserProjectWatchersCountService.call(observed_user)
@platform_project_watchers_count = Cache::PlatformProjectWatchersCountService.call
# 用户项目点赞数
@project_praises_count = Cache::UserProjectPraisesCountService.call(observed_user)
@platform_project_praises_count = Cache::PlatformProjectPraisesCountService.call
# 用户不同语言项目数量
@project_languages_count = Cache::UserProjectLanguagesCountService.call(observed_user)
@platform_project_languages_count = Cache::PlatformProjectLanguagesCountService.call
end
end
end

+ 17
- 8
app/controllers/users_controller.rb View File

@@ -6,6 +6,7 @@ class UsersController < ApplicationController
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]
before_action :convert_image!, only: [:update]
skip_before_action :check_sign, only: [:attachment_show]
def connect_to_ci_db(options={})
@@ -27,13 +28,15 @@ class UsersController < ApplicationController
def show
#待办事项,现在未做
if User.current.login == @user.login
if User.current.admin? || 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
@common_applied_projects = AppliedProject.where(project_id: @user.full_admin_projects).common
@undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + @common_applied_projects.size
else
@waiting_applied_messages = AppliedMessage.none
@common_applied_transfer_projects = AppliedTransferProject.none
@common_applied_projects = AppliedProject.none
@undo_events = 0
end
#用户的组织数量
@@ -62,7 +65,7 @@ class UsersController < ApplicationController
def fan_users
watchers = @user.watchers.includes(:user).order("watchers.created_at desc")
watchers = watchers.joins(:user).where("LOWER(concat(users.lastname, users.firstname, users.login)) LIKE ?", "%#{params[:search].split(" ").join('|')}%") if params[:search].present?
watchers = watchers.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.login, users.nickname)) LIKE ?", "%#{params[:search].split(" ").join('|')}%") if params[:search].present?
@watchers_count = watchers.size
@watchers = paginate(watchers)
@@ -72,9 +75,13 @@ class UsersController < ApplicationController
end
def update
@user = User.find params[:id]
@user.update!(user_params)
render_ok
return render_not_found unless @user = User.find_by_id(params[:id]) || User.find_by(login: params[:id])
return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id)
Util.write_file(@image, avatar_path(@user)) if user_params[:image].present?
@user.attributes = user_params.except(:image)
unless @user.save
render_error(@user.errors.full_messages.join(", "))
end
end
def me
@@ -311,11 +318,13 @@ class UsersController < ApplicationController
end
def user_params
params.require(:user).permit(:nickname, :lastname, :show_realname,:login,:mail,
params.require(:user).permit(:nickname, :image,
user_extension_attributes: [
:gender, :location, :location_city,
:occupation, :technical_title,
:school_id, :department_id,:identity, :student_id, :description]
:school_id, :department_id, :province, :city,
:custom_department, :identity, :student_id, :description,
:show_email, :show_location, :show_department]
)
end


+ 1
- 1
app/controllers/users_for_private_messages_controller.rb View File

@@ -11,7 +11,7 @@ class UsersForPrivateMessagesController < ApplicationController
return
end

users = users.where('LOWER(concat(lastname, firstname, nickname)) LIKE ?', "%#{keyword}%")
users = users.where('LOWER(CONCAT_WS(lastname, firstname, nickname)) LIKE ?', "%#{keyword}%")

@users = users.limit(10).includes(:user_extension)
end

+ 2
- 2
app/controllers/weapps/courses_controller.rb View File

@@ -86,7 +86,7 @@ class Weapps::CoursesController < Weapps::BaseController
end

if search.present?
@teacher_list = @teacher_list.joins(:user).where("LOWER(CONCAT(users.lastname, users.firstname)) like ?", "%#{search}%")
@teacher_list = @teacher_list.joins(:user).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.nickname)) like ?", "%#{search}%")
end

@teacher_list_size = @teacher_list.size
@@ -127,7 +127,7 @@ class Weapps::CoursesController < Weapps::BaseController
@students = CourseMember.students(@course)

if search.present?
@students = @students.joins(user: :user_extension).where("LOWER(CONCAT(users.lastname, users.firstname)) like ? or
@students = @students.joins(user: :user_extension).where("LOWER(CONCAT_WS(users.lastname, users.firstname, users.nickname)) like ? or
user_extensions.student_id like ?", "%#{search}%", "%#{search}%")
end



+ 2
- 2
app/controllers/zips_controller.rb View File

@@ -86,7 +86,7 @@ class ZipsController < ApplicationController
#搜索
if params[:search].present?
@ex_users = @ex_users.joins(user: :user_extension).where("CONCAT(lastname, firstname) like ? OR student_id like ?", "%#{params[:search]}%", "%#{params[:search]}%")
@ex_users = @ex_users.joins(user: :user_extension).where("CONCAT_WS(lastname, firstname, nickname) like ? OR student_id like ?", "%#{params[:search]}%", "%#{params[:search]}%")
end
default_ex_users_size = @ex_users&.size
@@ -130,7 +130,7 @@ class ZipsController < ApplicationController
end
unless params[:search].blank?
@all_student_works = @all_student_works.joins(user: :user_extension).where("concat(lastname, firstname) like ?
@all_student_works = @all_student_works.joins(user: :user_extension).where("CONCAT_WS(lastname, firstname, nickname) like ?
or student_id like ?", "%#{params[:search]}%", "%#{params[:search]}%")
end


+ 86
- 0
app/docs/slate/source/includes/_projects.md View File

@@ -1,5 +1,91 @@
# Projects

## 申请加入项目
申请加入项目

> 示例:

```shell
curl -X POST http://localhost:3000/api/applied_projects.json
```

```javascript
await octokit.request('POST /api/appliedr_projects.json')
```

### HTTP 请求
`POST /api/applied_projects.json`

### 请求参数
参数 | 必选 | 默认 | 类型 | 字段说明
--------- | ------- | ------- | -------- | ----------
|applied_project.code |是| |string |邀请码 |
|applied_project.role |否| |string |项目权限,reporter: 报告者, developer: 开发者,manager:管理员 |

> 请求的JSON示例

```json
{
"applied_project": {
"code": "1una34",
"role": "developer"
}
}
```

### 返回字段说明
参数 | 类型 | 字段说明
--------- | ----------- | -----------
|id |int |申请id |
|status |string |申请状态,canceled:取消,common:正在申请, accept:已接受,refuse:已拒绝|
|time_ago |string |项目申请创建的时间 |
|project.id |int |申请项目的id |
|project.identifier |string |申请项目的标识 |
|project.name |string |申请项目的名称 |
|project.description |string |申请项目的描述 |
|project.is_public |bool |申请项目是否公开 |
|project.owner.id |bool |申请项目拥有者id |
|project.owner.type |string |申请项目拥有者类型 |
|project.owner.name |string |申请项目拥有者昵称 |
|project.owner.login |string |申请项目拥有者标识 |
|project.owner.image_url |string |申请项目拥有者头像 |
|user.id |int |申请创建者的id |
|user.type |string |申请创建者的类型 |
|user.name |string |申请创建者的名称 |
|user.login |string |申请创建者的标识 |
|user.image_url |string |申请创建者头像 |
> 返回的JSON示例:

```json
{
"project": {
"id": 74,
"identifier": "hehuisssjssjjsjs",
"name": "hehuisssjssjjsjs",
"description": "wwww",
"is_public": false,
"owner": {
"id": 10,
"type": "User",
"name": "testforge1",
"login": "testforge1",
"image_url": "system/lets/letter_avatars/2/T/19_237_174/120.png"
}
},
"user": {
"id": 6,
"type": "User",
"name": "何慧",
"login": "yystopf",
"image_url": "images/avatars/User/6?t=1622513134"
},
"id": 7,
"status": "common",
"created_at": "2021-06-09 16:41",
"time_ago": "1分钟前"
}
```

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



+ 1196
- 6
app/docs/slate/source/includes/_users.md
File diff suppressed because it is too large
View File


+ 10
- 0
app/helpers/application_helper.rb View File

@@ -438,4 +438,14 @@ module ApplicationHelper
return nil if str.blank?
Base64.decode64 str
end
def render_admin_statistics_item
url = Rails.application.config_for(:configuration)["admin_statistics_url"]
return if url.blank?
content_tag(:li) do
sidebar_item(url, "数据统计", icon: 'bar-chart', controller: 'root')
end
end
end

+ 3
- 3
app/helpers/tag_chosen_helper.rb View File

@@ -94,7 +94,7 @@ module TagChosenHelper

def render_issue_tags(project)
# project.issue_tags.last&.cache_key
cache_key = "all_issue_tags/#{project.issue_tags.maximum('updated_at')}"
cache_key = "project-#{project.id}/all_issue_tags/size-#{project.issue_tags.size}/#{project.issue_tags.maximum('updated_at')}"

Rails.cache.fetch(cache_key) do
project.issue_tags.select(:id, :name, :color).collect do |event|
@@ -109,7 +109,7 @@ module TagChosenHelper
end

def render_cache_milestones(project)
cache_key = "all_milestones/#{project.versions.maximum('updated_on')}"
cache_key = "project-#{project.id}/all_milestones/size-#{project.versions.size}/#{project.versions.maximum('updated_on')}"

Rails.cache.fetch(cache_key) do
project.versions.select(:id, :name, :status).collect do |event|
@@ -124,7 +124,7 @@ module TagChosenHelper
end

def render_cache_collaborators(project)
cache_key = "all_collaborators/#{project.all_collaborators.maximum('created_on')}"
cache_key = "project-#{project.id}/all_collaborators/size-#{project.all_collaborators.size}/#{project.all_collaborators.maximum('updated_on')}"
Rails.cache.fetch(cache_key) do
project.all_collaborators.order(created_on: :desc).collect do |user|
{


+ 16
- 1
app/interactors/projects/add_member_interactor.rb View File

@@ -23,7 +23,7 @@ module Projects
ActiveRecord::Base.transaction do
gitea_result = Gitea::Repository::Members::AddService.new(owner, project.identifier, collaborator.login, permission).call
if gitea_result.status == 204
project.add_member!(collaborator.id)
project.add_member!(collaborator.id, role_name)
end
fail!(nil)
end
@@ -38,5 +38,20 @@ module Projects
@error = error
end

def role_name
case permission
when 'read'
'Reporter'
when 'write'
'Developer'
when 'admin'
'Manager'
when 'owner'
'Manager'
else
'Reporter'
end
end

end
end

+ 14
- 0
app/jobs/reset_platform_cache_job.rb View File

@@ -0,0 +1,14 @@
class ResetPlatformCacheJob < ApplicationJob
queue_as :cache

def perform
Cache::PlatformFollowCountService.new.reset
Cache::PlatformIssueCountService.new.reset
Cache::PlatformProjectCountService.new.reset
Cache::PlatformProjectForkCountService.new.reset
Cache::PlatformProjectLanguagesCountService.new.reset
Cache::PlatformProjectPraisesCountService.new.reset
Cache::PlatformProjectWatchersCountService.new.reset
Cache::PlatformPullrequestCountService.new.reset
end
end

+ 14
- 0
app/jobs/reset_user_cache_job.rb View File

@@ -0,0 +1,14 @@
class ResetUserCacheJob < ApplicationJob
queue_as :cache

def perform(user)
Cache::UserFollowCountService.new(user).reset
Cache::UserIssueCountService.new(user).reset
Cache::UserProjectCountService.new(user).reset
Cache::UserProjectForkCountService.new(user).reset
Cache::UserProjectLanguagesCountService.new(user).reset
Cache::UserProjectPraisesCountService.new(user).reset
Cache::UserProjectWatchersCountService.new(user).reset
Cache::UserPullrequestCountService.new(user).reset
end
end

+ 27
- 0
app/jobs/send_join_project_applied_message_job.rb View File

@@ -0,0 +1,27 @@
class SendJoinProjectAppliedMessageJob < ApplicationJob
queue_as :default

def perform(applied_project, applied_user, message_status)
project = applied_project.project
return unless project.present?
return unless applied_user.present?
return unless applied_project.user.present?
AppliedMessage.find_or_create_by!(user_id: applied_project.user_id,
applied: applied_project,
status: message_status,
name: build_name(project.name, message_status),
applied_user_id: applied_user.id,
project_id: project.id)
end

private
def build_name(repo_name, message_status, applied_name="")
case message_status
when 'successed'
return "已通过你加入【#{repo_name}】仓库的申请。"
when 'failure'
return "已拒绝你加入【#{repo_name}】仓库的申请。"
end
""
end
end

+ 21
- 1
app/jobs/sync_mirrored_repository_job.rb View File

@@ -5,7 +5,27 @@ class SyncMirroredRepositoryJob < ApplicationJob
repo = Repository.find_by(id: repo_id)
current_user = User.find_by(id: user_id)
return if repo.blank? || current_user.blank?
result = Gitea::Repository::SyncMirroredService.new(repo.owner.login, repo.identifier, token: current_user.gitea_token).call

# TODO
# 先同步镜像库
if repo.config_accelerator?
puts "[gitea-accelerator]: ###### 镜像库开始同步 ######"
result = Gitea::Accelerator::SyncMirroredService.call(repo.identifier)
puts "[gitea-accelerator]: ###### 镜像库同步状态为 #{result[:status]}"

# TODO 暂时解决从镜像库镜像动作时间先执行的问题
# 避免了Gitea::Repository::SyncMirroredService先执行后,加速器Gitea::Accelerator::SyncMirroredService
# 再执行的导致Gitea::Repository::SyncMirroredService执行镜像不是最新代码的问题
sleep 3.seconds
end

sync_common!(repo, current_user)
end

def sync_common!(repo, user)
result = Gitea::Repository::SyncMirroredService.call(repo.owner.login,
repo.identifier, token: user.gitea_token)
repo&.mirror.set_status! if result[:status] === 200
end

end

+ 8
- 0
app/models/application_record.rb View File

@@ -16,4 +16,12 @@ class ApplicationRecord < ActiveRecord::Base
def allow_sync_to_trustie?
Rails.env.production? && EduSetting.get('host_name') == 'https://www.educoder.net'
end
def reset_user_cache_async_job(user)
ResetUserCacheJob.perform_later(user)
end
def reset_platform_cache_async_job
ResetPlatformCacheJob.perform_later
end
end

+ 6
- 2
app/models/applied_project.rb View File

@@ -7,6 +7,8 @@
# user_id :integer not null
# role :integer default("0")
# status :integer default("0")
# created_at :datetime
# updated_at :datetime
#

class AppliedProject < ApplicationRecord
@@ -14,7 +16,9 @@ class AppliedProject < ApplicationRecord
belongs_to :project

has_many :applied_messages, as: :applied, dependent: :destroy
has_many :forge_activities, as: :forge_act, dependent: :destroy
# has_many :forge_activities, as: :forge_act, dependent: :destroy

enum role: {manager: 3, developer: 4, reporter: 5}
enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝

scope :pending, -> { where(status: 0) }
end

+ 6
- 5
app/models/attachment.rb View File

@@ -17,7 +17,7 @@
# disk_directory :string(255)
# attachtype :integer default("1")
# is_public :integer default("1")
# copy_from :string(255)
# copy_from :integer
# quotes :integer default("0")
# is_publish :integer default("1")
# publish_time :datetime
@@ -26,17 +26,18 @@
# cloud_url :string(255) default("")
# course_second_category_id :integer default("0")
# delay_publish :boolean default("0")
# link :string(255)
# clone_id :integer
#
# Indexes
#
# index_attachments_on_author_id (author_id)
# index_attachments_on_clone_id (clone_id)
# index_attachments_on_container_id_and_container_type (container_id,container_type)
# index_attachments_on_course_second_category_id (course_second_category_id)
# index_attachments_on_created_on (created_on)
# index_attachments_on_is_public (is_public)
# index_attachments_on_quotes (quotes)
#

class Attachment < ApplicationRecord
include BaseModel
include Publicable
@@ -51,7 +52,7 @@ class Attachment < ApplicationRecord
# 二级目录
# belongs_to :course_second_category, optional: true
scope :by_filename_or_user_name, -> (keywords) { joins(:author).where("filename like :search or LOWER(concat(users.lastname, users.firstname)) LIKE :search",
scope :by_filename_or_user_name, -> (keywords) { joins(:author).where("filename like :search or LOWER(CONCAT_WS(users.lastname, users.firstname, users.nickname)) LIKE :search",
:search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? }
scope :by_keywords, -> (keywords) { where("filename LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank? }
scope :ordered, -> (opts = {}) { order("#{opts[:sort_type]} #{opts[:sort] == 1 ? 'asc': 'desc'}") }


+ 9
- 8
app/models/ci/user.rb View File

@@ -39,15 +39,17 @@
# business :boolean default("0")
# profile_completed :boolean default("0")
# laboratory_id :integer
# is_shixun_marker :boolean default("0")
# admin_visitable :boolean default("0")
# collaborator :boolean default("0")
# platform :string(255) default("0")
# gitea_token :string(255)
# gitea_uid :integer
# is_shixun_marker :boolean default("0")
# is_sync_pwd :boolean default("1")
# watchers_count :integer default("0")
# devops_step :integer default("0")
# gitea_token :string(255)
# platform :string(255)
# sponsor_certification :integer default("0")
# sponsor_num :integer default("0")
# sponsored_num :integer default("0")
# award_time :datetime
#
# Indexes
#
@@ -55,9 +57,8 @@
# index_users_on_homepage_engineer (homepage_engineer)
# index_users_on_homepage_teacher (homepage_teacher)
# index_users_on_laboratory_id (laboratory_id)
# index_users_on_login (login) UNIQUE
# index_users_on_mail (mail) UNIQUE
# index_users_on_phone (phone) UNIQUE
# index_users_on_login (login)
# index_users_on_mail (mail)
# index_users_on_type (type)
#



+ 28
- 0
app/models/concerns/dcodes.rb View File

@@ -0,0 +1,28 @@
module Dcodes
DCODES = %W(1 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 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)

extend ActiveSupport::Concern

def generate_dcode(field, num, pre='')
code = DCODES.sample(num).join
while self.class.exists?("#{field}": pre+code) do
code = DCODES.sample(num).join
end
code
end

def init_project_invite_code
while Project.where(invite_code: nil).size > 0 do
projects = Project.where(invite_code: nil).limit(1000)
set_sql = ""
projects.each do |p|
set_sql += "WHEN #{p.id} THEN '#{DCODES.sample(6).join}' "
end
sql = "UPDATE projects SET invite_code = CASE id "+ set_sql+ "END WHERE id IN(#{projects.ids.join(",")})"
Project.connection.execute(sql)
end
repeat_codes = Project.group(:invite_code).count.select{|k,v| v>1}
Project.where(invite_code: repeat_code.keys).update_all(invite_code: nil)
end

end

+ 3
- 2
app/models/concerns/project_operable.rb View File

@@ -9,6 +9,7 @@ module ProjectOperable
has_many :reporters, -> { joins(:roles).where(roles: { name: 'Reporter' }) }, class_name: 'Member'
has_many :writable_members, -> { joins(:roles).where.not(roles: {name: 'Reporter'}) }, class_name: 'Member'
has_many :team_projects, dependent: :destroy
has_many :teams, through: :team_projects, source: :team
end

def set_owner_permission(creator)
@@ -64,7 +65,7 @@ module ProjectOperable
if owner.is_a?(User)
managers.exists?(user_id: user.id)
elsif owner.is_a?(Organization)
managers.exists?(user_id: user.id) || owner.is_admin?(user.id)
managers.exists?(user_id: user.id) || owner.is_only_admin?(user.id)
else
false
end
@@ -75,7 +76,7 @@ module ProjectOperable
if owner.is_a?(User)
developers.exists?(user_id: user.id)
elsif owner.is_a?(Organization)
developers.exists?(user_id: user.id) || owner.is_write?(user.id)
developers.exists?(user_id: user.id) || owner.is_only_write?(user.id)
else
false
end


+ 8
- 0
app/models/fork_user.rb View File

@@ -20,4 +20,12 @@ class ForkUser < ApplicationRecord
belongs_to :user
belongs_to :fork_project, class_name: 'Project', foreign_key: :fork_project_id

after_save :reset_cache_data
after_destroy :reset_cache_data

def reset_cache_data
self.reset_platform_cache_async_job
self.reset_user_cache_async_job(self.project.owner)
end

end

+ 8
- 3
app/models/issue.rb View File

@@ -50,7 +50,7 @@

class Issue < ApplicationRecord
#issue_type 1为普通,2为悬赏
belongs_to :project, :counter_cache => true
belongs_to :project, counter_cache: true, touch: true
belongs_to :tracker,optional: true
has_many :project_trends, as: :trend, dependent: :destroy
has_one :pull_request
@@ -73,10 +73,15 @@ class Issue < ApplicationRecord
scope :issue_issue, ->{where(issue_classify: [nil,"issue"])}
scope :issue_pull_request, ->{where(issue_classify: "pull_request")}
scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
scope :closed, ->{where(status_id: 5)}
after_update :change_versions_count
after_destroy :update_closed_issues_count_in_project!
after_save :reset_cache_data
after_destroy :update_closed_issues_count_in_project!, :reset_cache_data

def reset_cache_data
self.reset_platform_cache_async_job
self.reset_user_cache_async_job(self.user)
end

def get_assign_user
User&.find_by_id(self.assigned_to_id) if self.assigned_to_id.present?


+ 0
- 1
app/models/laboratory.rb View File

@@ -10,7 +10,6 @@
# sync_course :boolean default("0")
# sync_subject :boolean default("0")
# sync_shixun :boolean default("0")
# is_local :boolean default("0")
#
# Indexes
#


+ 1
- 0
app/models/license.rb View File

@@ -7,6 +7,7 @@
# content :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
# is_secret :boolean default("0")
#

class License < ApplicationRecord


+ 1
- 0
app/models/member.rb View File

@@ -11,6 +11,7 @@
# course_group_id :integer default("0")
# is_collect :integer default("1")
# graduation_group_id :integer default("0")
# is_apply_signature :boolean default("0")
#
# Indexes
#


+ 17
- 8
app/models/organization.rb View File

@@ -39,15 +39,17 @@
# business :boolean default("0")
# profile_completed :boolean default("0")
# laboratory_id :integer
# is_shixun_marker :boolean default("0")
# admin_visitable :boolean default("0")
# collaborator :boolean default("0")
# platform :string(255) default("0")
# gitea_token :string(255)
# gitea_uid :integer
# is_shixun_marker :boolean default("0")
# is_sync_pwd :boolean default("1")
# watchers_count :integer default("0")
# devops_step :integer default("0")
# gitea_token :string(255)
# platform :string(255)
# sponsor_certification :integer default("0")
# sponsor_num :integer default("0")
# sponsored_num :integer default("0")
# award_time :datetime
#
# Indexes
#
@@ -55,9 +57,8 @@
# index_users_on_homepage_engineer (homepage_engineer)
# index_users_on_homepage_teacher (homepage_teacher)
# index_users_on_laboratory_id (laboratory_id)
# index_users_on_login (login) UNIQUE
# index_users_on_mail (mail) UNIQUE
# index_users_on_phone (phone) UNIQUE
# index_users_on_login (login)
# index_users_on_mail (mail)
# index_users_on_type (type)
#

@@ -109,6 +110,14 @@ class Organization < Owner
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).present?
end

def is_only_admin?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(admin)}).present?
end

def is_only_write?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(write)}).present?
end

def is_only_read?(user_id)
team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read)}).present?
end


+ 22
- 0
app/models/pinned_project.rb View File

@@ -0,0 +1,22 @@
# == Schema Information
#
# Table name: pinned_projects
#
# id :integer not null, primary key
# user_id :integer
# project_id :integer
# position :integer default("0")
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_pinned_projects_on_project_id (project_id)
# index_pinned_projects_on_user_id (user_id)
#

class PinnedProject < ApplicationRecord

belongs_to :user
belongs_to :project
end

+ 10
- 0
app/models/praise_tread.rb View File

@@ -15,12 +15,22 @@
# praise_tread (praise_tread_object_id,praise_tread_object_type)
#

class PraiseTread < ApplicationRecord
belongs_to :user
belongs_to :praise_tread_object, polymorphic: true, counter_cache: :praises_count
has_many :tidings, :as => :container, :dependent => :destroy
after_create :send_tiding
after_save :reset_cache_data
after_destroy :reset_cache_data
def reset_cache_data
self.reset_platform_cache_async_job
if self.praise_tread_object.is_a?(Project)
self.reset_user_cache_async_job(self.praise_tread_object&.owner)
end
end
def send_tiding
case self.praise_tread_object_type


+ 95
- 75
app/models/project.rb View File

@@ -1,82 +1,85 @@
# == 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)
#

# == 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)
# order_index :integer default("0")
# 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
include Publicable
include Watchable
include ProjectOperable
include Dcodes
# common:开源托管项目
# mirror:普通镜像项目,没有定时同步功能
@@ -112,11 +115,14 @@ class Project < ApplicationRecord
has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
has_one :project_detail, dependent: :destroy
has_many :team_projects, dependent: :destroy
has_many :project_units, dependent: :destroy
has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy
has_many :pinned_projects, dependent: :destroy
has_many :has_pinned_users, through: :pinned_projects, source: :user
after_save :check_project_members
after_save :check_project_members, :reset_cache_data
before_save :set_invite_code
after_destroy :reset_cache_data
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)}
scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)}
scope :recommend, -> { visible.project_statics_select.where(recommend: true) }
@@ -124,6 +130,20 @@ class Project < ApplicationRecord
delegate :content, to: :project_detail, allow_nil: true
delegate :name, to: :license, prefix: true, allow_nil: true
def reset_cache_data
if changes[:user_id].present?
first_owner = Owner.find_by_id(changes[:user_id].first)
self.reset_user_cache_async_job(first_owner)
end
self.reset_platform_cache_async_job
self.reset_user_cache_async_job(self.owner)
end
def set_invite_code
if self.invite_code.nil?
self.invite_code= self.generate_dcode('invite_code', 6)
end
end
def self.search_project(search)
ransack(name_or_identifier_cont: search)


+ 5
- 0
app/models/project_category.rb View File

@@ -8,6 +8,11 @@
# projects_count :integer default("0")
# created_at :datetime not null
# updated_at :datetime not null
# ancestry :string(255)
#
# Indexes
#
# index_project_categories_on_ancestry (ancestry)
#

class ProjectCategory < ApplicationRecord


+ 11
- 1
app/models/pull_request.rb View File

@@ -31,13 +31,23 @@ class PullRequest < ApplicationRecord

belongs_to :issue
belongs_to :user
belongs_to :project, :counter_cache => true
belongs_to :project, counter_cache: true, touch: true
# belongs_to :fork_project, foreign_key: :fork_project_id
has_many :pull_request_assigns, foreign_key: :pull_request_id
has_many :pull_request_tags, foreign_key: :pull_request_id
has_many :project_trends, as: :trend, dependent: :destroy
has_many :attachments, as: :container, dependent: :destroy

scope :merged_and_closed, ->{where.not(status: 1)}

after_save :reset_cache_data
after_destroy :reset_cache_data

def reset_cache_data
self.reset_platform_cache_async_job
self.reset_user_cache_async_job(self.user)
end

def fork_project
Project.find_by(id: self.fork_project_id)
end


+ 4
- 0
app/models/repository.rb View File

@@ -27,6 +27,7 @@
#
# Indexes
#
# index_repositories_on_identifier (identifier)
# index_repositories_on_project_id (project_id)
# index_repositories_on_user_id (user_id)
#
@@ -82,5 +83,8 @@ class Repository < ApplicationRecord
source_clone_url.blank? ? mirror_url : source_clone_url
end
def config_accelerator?
!source_clone_url.blank?
end

end

+ 31
- 9
app/models/user.rb View File

@@ -39,15 +39,17 @@
# business :boolean default("0")
# profile_completed :boolean default("0")
# laboratory_id :integer
# is_shixun_marker :boolean default("0")
# admin_visitable :boolean default("0")
# collaborator :boolean default("0")
# platform :string(255) default("0")
# gitea_token :string(255)
# gitea_uid :integer
# is_shixun_marker :boolean default("0")
# is_sync_pwd :boolean default("1")
# watchers_count :integer default("0")
# devops_step :integer default("0")
# gitea_token :string(255)
# platform :string(255)
# sponsor_certification :integer default("0")
# sponsor_num :integer default("0")
# sponsored_num :integer default("0")
# award_time :datetime
#
# Indexes
#
@@ -55,9 +57,8 @@
# index_users_on_homepage_engineer (homepage_engineer)
# index_users_on_homepage_teacher (homepage_teacher)
# index_users_on_laboratory_id (laboratory_id)
# index_users_on_login (login) UNIQUE
# index_users_on_mail (mail) UNIQUE
# index_users_on_phone (phone) UNIQUE
# index_users_on_login (login)
# index_users_on_mail (mail)
# index_users_on_type (type)
#

@@ -164,6 +165,11 @@ class User < Owner

has_many :organization_users, dependent: :destroy
has_many :organizations, through: :organization_users
has_many :pinned_projects, dependent: :destroy
has_many :is_pinned_projects, through: :pinned_projects, source: :project
accepts_nested_attributes_for :is_pinned_projects
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :pull_requests, dependent: :destroy

# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
@@ -176,7 +182,9 @@ class User < Owner

attr_accessor :password, :password_confirmation

delegate :gender, :department_id, :school_id, :location, :location_city, :technical_title, to: :user_extension, allow_nil: true
delegate :description, :gender, :department_id, :school_id, :location, :location_city,
:show_email, :show_location, :show_department,
:technical_title, :province, :city, :custom_department, to: :user_extension, allow_nil: true

before_save :update_hashed_password
after_create do
@@ -195,6 +203,20 @@ class User < Owner
validate :validate_sensitive_string
validate :validate_password_length

# 用户参与的所有项目
def full_member_projects
normal_projects = Project.members_projects(self.id).to_sql
org_projects = Project.joins(teams: :team_users).where(team_users: {user_id: self.id}).to_sql
return Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
end

# 用户管理的所有项目
def full_admin_projects
normal_projects = Project.joins(members: :roles).where(roles: {name: 'Manager'}, members: {user_id: self.id}).to_sql
org_projects = Project.joins(teams: :team_users).where(teams: {authorize: %w(admin owner)}, team_users: {user_id: self.id}).to_sql
return Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct
end

def name
login
end


+ 1
- 3
app/models/user_action.rb View File

@@ -12,9 +12,7 @@
#
# Indexes
#
# index_user_actions_on_ip (ip)
# index_user_actions_on_user_id (user_id)
# index_user_actions_on_user_id_and_action_type (user_id,action_type)
# index_user_actions_on_ip (ip)
#

class UserAction < ApplicationRecord


+ 1
- 4
app/models/user_agent.rb View File

@@ -10,13 +10,10 @@
# updated_at :datetime not null
# register_status :integer default("0")
# action_status :integer default("0")
# is_delete :boolean default("0")
# user_id :integer
#
# Indexes
#
# index_user_agents_on_ip (ip)
# index_user_agents_on_user_id (user_id)
# index_user_agents_on_ip (ip) UNIQUE
#

class UserAgent < ApplicationRecord


+ 28
- 25
app/models/user_extension.rb View File

@@ -22,9 +22,12 @@
# school_id :integer
# description :string(255) default("")
# department_id :integer
# honor :text(65535)
# edu_background :integer
# edu_entry_year :integer
# province :string(255)
# city :string(255)
# custom_department :string(255)
# show_email :boolean default("0")
# show_location :boolean default("0")
# show_department :boolean default("0")
#
# Indexes
#
@@ -33,25 +36,25 @@
# index_user_extensions_on_user_id (user_id)
#

class UserExtension < ApplicationRecord
# identity 0: 教师教授 1: 学生, 2: 专业人士, 3: 开发者
enum identity: { teacher: 0, student: 1, professional: 2, developer: 3, enterprise: 4, unselect: -1 }
belongs_to :user, touch: true
belongs_to :school, optional: true
# belongs_to :department, optional: true
# before_save :set_laboratory_school
def identity_text
I18n.t("user.identity.#{identity}")
end
private
def set_laboratory_school
# return unless new_record?
# self.school_id = Laboratory.current.school_id if school_id.blank? && !Laboratory.current.main_site?
end
end
class UserExtension < ApplicationRecord
# identity 0: 教师教授 1: 学生, 2: 专业人士, 3: 开发者
enum identity: { teacher: 0, student: 1, professional: 2, developer: 3, enterprise: 4, unselect: -1 }
belongs_to :user, touch: true
belongs_to :school, optional: true
# belongs_to :department, optional: true
# before_save :set_laboratory_school
def identity_text
I18n.t("user.identity.#{identity}")
end
private
def set_laboratory_school
# return unless new_record?
# self.school_id = Laboratory.current.school_id if school_id.blank? && !Laboratory.current.main_site?
end
end

+ 2
- 1
app/models/version.rb View File

@@ -24,11 +24,12 @@
#

class Version < ApplicationRecord
belongs_to :project, counter_cache: true, optional: true
belongs_to :project, counter_cache: true, touch: true, optional: true
has_many :issues, class_name: "Issue", foreign_key: "fixed_version_id"
belongs_to :user, optional: true

scope :version_includes, ->{includes(:issues, :user)}
scope :closed, ->{where(status: 'closed')}

# def open_issues_count
# issues.select(:id,:status_id).where(status_id: [1,2,3,4,6]).size


+ 21
- 7
app/models/watcher.rb View File

@@ -15,10 +15,24 @@
# watchers_user_id_type (user_id,watchable_type)
#

class Watcher < ApplicationRecord
belongs_to :user
belongs_to :watchable, polymorphic: true, counter_cache: :watchers_count
scope :watching_users, ->(watchable_id){ where("watchable_type = ? and user_id = ?",'User',watchable_id)}
end
class Watcher < ApplicationRecord
belongs_to :user
belongs_to :watchable, polymorphic: true, counter_cache: :watchers_count

scope :watching_users, ->(watchable_id){ where("watchable_type = ? and user_id = ?",'User',watchable_id)}

after_save :reset_cache_data
after_destroy :reset_cache_data

def reset_cache_data
if self.watchable.is_a?(User)
self.reset_user_cache_async_job(self.watchable)
end
if self.watchable.is_a?(Project)
self.reset_user_cache_async_job(self.watchable&.owner)
end
self.reset_platform_cache_async_job
end

end

+ 1
- 1
app/queries/admins/apply_item_bank_query.rb View File

@@ -26,7 +26,7 @@ class Admins::ApplyItemBankQuery < ApplicationQuery
keyword = params[:keyword].to_s.strip
if keyword.present?
applies = applies.joins(user: { user_extension: :school })
.where('CONCAT(lastname,firstname) LIKE :keyword OR schools.name LIKE :keyword', keyword: "%#{keyword}%")
.where('CONCAT_WS(lastname,firstname, nickname) LIKE :keyword OR schools.name LIKE :keyword', keyword: "%#{keyword}%")
end

custom_sort(applies, params[:sort_by], params[:sort_direction])


+ 1
- 1
app/queries/admins/apply_user_authentication_query.rb View File

@@ -26,7 +26,7 @@ class Admins::ApplyUserAuthenticationQuery < ApplicationQuery
keyword = params[:keyword].to_s.strip
if keyword.present?
applies = applies.joins(user: { user_extension: :school })
.where('CONCAT(lastname,firstname) LIKE :keyword OR schools.name LIKE :keyword', keyword: "%#{keyword}%")
.where('CONCAT_WS(lastname,firstname,nickname) LIKE :keyword OR schools.name LIKE :keyword', keyword: "%#{keyword}%")
end

custom_sort(applies, params[:sort_by], params[:sort_direction])


+ 1
- 1
app/queries/admins/course_list_query.rb View File

@@ -19,7 +19,7 @@ class Admins::CourseListQuery < ApplicationQuery
case search_type
when "0"
course_lists = course_lists.joins(:user)
.where('CONCAT(lastname, firstname) like :keyword', keyword: "%#{keyword}%")
.where('CONCAT_WS(lastname, firstname, nickname) like :keyword', keyword: "%#{keyword}%")
when "1"
course_lists = course_lists.where('name like :keyword', keyword: "%#{keyword}%")
end


+ 1
- 1
app/queries/admins/course_query.rb View File

@@ -35,7 +35,7 @@ class Admins::CourseQuery < ApplicationQuery
# 关键字
keyword = params[:keyword].to_s.strip
if keyword
sql = 'CONCAT(lastname, firstname) LIKE :keyword OR courses.name LIKE :keyword OR course_lists.name LIKE :keyword'
sql = 'CONCAT_WS(lastname, firstname, nickname) LIKE :keyword OR courses.name LIKE :keyword OR course_lists.name LIKE :keyword'
courses = courses.joins(:teacher, :course_list).where(sql, keyword: "%#{keyword}%")
end



+ 1
- 1
app/queries/admins/laboratory_shixun_query.rb View File

@@ -11,7 +11,7 @@ class Admins::LaboratoryShixunQuery < ApplicationQuery

keyword = params[:keyword].to_s.strip
if keyword.present?
like_sql = 'shixuns.name LIKE :keyword OR CONCAT(users.lastname, users.firstname) LIKE :keyword'
like_sql = 'shixuns.name LIKE :keyword OR CONCAT_WS(users.lastname, users.firstname, users.nickname) LIKE :keyword'
laboratory_shixuns = laboratory_shixuns.joins(shixun: :user).where(like_sql, keyword: "%#{keyword}%")
end



+ 1
- 1
app/queries/admins/laboratory_subject_query.rb View File

@@ -11,7 +11,7 @@ class Admins::LaboratorySubjectQuery < ApplicationQuery

keyword = params[:keyword].to_s.strip
if keyword.present?
like_sql = 'subjects.name LIKE :keyword OR CONCAT(users.lastname, users.firstname) LIKE :keyword'
like_sql = 'subjects.name LIKE :keyword OR CONCAT_WS(users.lastname, users.firstname, users.nickname) LIKE :keyword'
laboratory_subjects = laboratory_subjects.joins(subject: :user).where(like_sql, keyword: "%#{keyword}%")
end



+ 1
- 1
app/queries/admins/subject_query.rb View File

@@ -40,7 +40,7 @@ class Admins::SubjectQuery < ApplicationQuery
# 关键字
keyword = params[:keyword].to_s.strip
if keyword
sql = 'CONCAT(lastname, firstname) LIKE :keyword OR subjects.name LIKE :keyword'
sql = 'CONCAT_WS(lastname, firstname, nickname) LIKE :keyword OR subjects.name LIKE :keyword'
subjects = subjects.joins(:user).where(sql, keyword: "%#{keyword}%")
end



+ 2
- 2
app/queries/admins/user_query.rb View File

@@ -30,14 +30,14 @@ class Admins::UserQuery < ApplicationQuery
# 关键字检索
keyword = params[:keyword].to_s.strip.presence
if keyword
sql = 'CONCAT(lastname, firstname) LIKE :keyword OR login LIKE :keyword OR mail LIKE :keyword OR phone LIKE :keyword'
sql = 'CONCAT_WS(lastname, firstname, nickname) LIKE :keyword OR login LIKE :keyword OR mail LIKE :keyword OR phone LIKE :keyword'
users = users.where(sql, keyword: "%#{keyword}%")
end

# 姓名
name = params[:name].to_s.strip.presence
if name.present?
users = users.where('CONCAT(lastname, firstname) LIKE :name', name: "%#{name}%")
users = users.where('CONCAT_WS(lastname, firstname, nickname) LIKE :name', name: "%#{name}%")
end

# 单位ID


+ 7
- 2
app/queries/projects/list_my_query.rb View File

@@ -53,10 +53,15 @@ class Projects::ListMyQuery < ApplicationQuery
q = projects.ransack(name_or_identifier_cont: params[:search])

scope = q.result.includes(:project_category, :project_language,:owner, :repository)
scope = q.result.includes(:project_category, :project_language,:owner, :repository, :has_pinned_users)

sort = params[:sort_by] || "updated_on"
sort_direction = params[:sort_direction] || "desc"
scope.order("projects.#{sort} #{sort_direction}")
if params[:choosed].present? && params[:choosed].is_a?(Array)
scope.order("FIELD(id, #{params[:choosed].reverse.join(",")}) desc")
else
scope.order("projects.#{sort} #{sort_direction}")
end
end
end

+ 1
- 1
app/queries/user_query.rb View File

@@ -10,7 +10,7 @@ class UserQuery < ApplicationQuery

# 真实姓名
if name = strip_param(:name)
users = users.where('LOWER(CONCAT(users.lastname, users.firstname)) LIKE ?', "%#{name.downcase}%")
users = users.where('LOWER(CONCAT_WS(users.lastname, users.firstname, users.nickname)) LIKE ?', "%#{name.downcase}%")
end

# 单位名称


+ 1
- 1
app/services/admins/import_user_service.rb View File

@@ -84,7 +84,7 @@ class Admins::ImportUserService < ApplicationService
if data.identity.to_i == 1
users = users.where(user_extensions: { student_id: data.student_id })
else
users = users.where(user_extensions: { technical_title: data.technical_title }).where('CONCAT(users.lastname,users.firstname) = ?', data.name)
users = users.where(user_extensions: { technical_title: data.technical_title }).where('CONCAT_WS(users.lastname,users.firstname,users.nickname) = ?', data.name)
end

users.first


+ 41
- 0
app/services/cache/platform_follow_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformFollowCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_follow_count

platform_follow_count
end

def reset
reset_platform_follow_count

platform_follow_count
end

private

def platform_follow_count_key
"platform-follow-count"
end

def platform_follow_count
$redis_cache.get(platform_follow_count_key).to_i
end

def set_platform_follow_count
if $redis_cache.exists(platform_follow_count_key)
$redis_cache.incrby(platform_follow_count_key, increment_count)
else
reset_platform_follow_count
end
end

def reset_platform_follow_count
$redis_cache.set(platform_follow_count_key, Watcher.where(watchable_type: 'User').count)
end
end

+ 41
- 0
app/services/cache/platform_issue_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformIssueCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_issue_count

platform_issue_count
end

def reset
reset_platform_issue_count

platform_issue_count
end

private

def platform_issue_count_key
"platform-issue-count"
end

def platform_issue_count
$redis_cache.get(platform_issue_count_key).to_i
end

def set_platform_issue_count
if $redis_cache.exists(platform_issue_count_key)
$redis_cache.incrby(platform_issue_count_key, increment_count)
else
reset_platform_issue_count
end
end

def reset_platform_issue_count
$redis_cache.set(platform_issue_count_key, Issue.count)
end
end

+ 41
- 0
app/services/cache/platform_project_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformProjectCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_project_count

platform_project_count
end

def reset
reset_platform_project_count

platform_project_count
end

private

def platform_project_count_key
"platform-project-count"
end

def platform_project_count
$redis_cache.get(platform_project_count_key).to_i
end

def set_platform_project_count
if $redis_cache.exists(platform_project_count_key)
$redis_cache.incrby(platform_project_count_key, increment_count)
else
reset_platform_project_count
end
end

def reset_platform_project_count
$redis_cache.set(platform_project_count_key, Project.count)
end
end

+ 41
- 0
app/services/cache/platform_project_fork_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformProjectForkCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_project_fork_count

platform_project_fork_count
end

def reset
reset_platform_project_fork_count

platform_project_fork_count
end

private

def platform_project_fork_count_key
"platform-project-fork-count"
end

def platform_project_fork_count
$redis_cache.get(platform_project_fork_count_key).to_i
end

def set_platform_project_fork_count
if $redis_cache.exists(platform_project_fork_count_key)
$redis_cache.incrby(platform_project_fork_count_key, increment_count)
else
reset_platform_project_fork_count
end
end

def reset_platform_project_fork_count
$redis_cache.set(platform_project_fork_count_key, ForkUser.count)
end
end

+ 57
- 0
app/services/cache/platform_project_languages_count_service.rb View File

@@ -0,0 +1,57 @@
class Cache::PlatformProjectLanguagesCountService < ApplicationService
attr_reader :key, :increment_count

def initialize(key=nil, increment_count=0)
@key = key
@increment_count = increment_count
end

def call
set_platform_project_language_count

platform_project_language_count
end

def reset_by_key
reset_platform_project_language_count_by_key

platform_project_language_count
end

def reset
reset_platform_project_language_count

platform_project_language_count
end

private

def platform_project_language_count_key
"platform-project-language-count"
end

def platform_project_language_count
$redis_cache.hgetall(platform_project_language_count_key).transform_values(&:to_i)
end

def set_platform_project_language_count
if $redis_cache.hlen(platform_project_language_count_key) == 0
reset_platform_project_language_count
elsif $redis_cache.hget(platform_project_language_count_key, key).nil?
reset_platform_project_language_count_by_key
else
$redis_cache.hincrby(platform_project_language_count_key, key, increment_count)
end
end

def reset_platform_project_language_count_by_key
return if key.nil?
$redis_cache.hset(platform_project_language_count_key, key, Project.joins(:project_language).where(project_languages: {name: key}).count)
end

def reset_platform_project_language_count
Project.joins(:project_language).group("project_languages.name").count.each do |k, v|
$redis_cache.hset(platform_project_language_count_key, k, v)
end
end
end

+ 41
- 0
app/services/cache/platform_project_praises_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformProjectPraisesCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_project_praises_count

platform_project_praises_count
end

def reset
reset_platform_project_praises_count

platform_project_praises_count
end

private

def platform_project_praises_count_key
"platform-project-praises-count"
end

def platform_project_praises_count
$redis_cache.get(platform_project_praises_count_key).to_i
end

def set_platform_project_praises_count
if $redis_cache.exists(platform_project_praises_count_key)
$redis_cache.incrby(platform_project_praises_count_key, increment_count)
else
reset_platform_project_praises_count
end
end

def reset_platform_project_praises_count
$redis_cache.set(platform_project_praises_count_key, PraiseTread.where(praise_tread_object_type: "Project").count)
end
end

+ 41
- 0
app/services/cache/platform_project_watchers_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformProjectWatchersCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_project_watchers_count

platform_project_watchers_count
end

def reset
reset_platform_project_watchers_count

platform_project_watchers_count
end

private

def platform_project_watchers_count_key
"platform-project-watchers-count"
end

def platform_project_watchers_count
$redis_cache.get(platform_project_watchers_count_key).to_i
end

def set_platform_project_watchers_count
if $redis_cache.exists(platform_project_watchers_count_key)
$redis_cache.incrby(platform_project_watchers_count_key, increment_count)
else
reset_platform_project_watchers_count
end
end

def reset_platform_project_watchers_count
$redis_cache.set(platform_project_watchers_count_key, Watcher.where(watchable_type: 'Project').count)
end
end

+ 41
- 0
app/services/cache/platform_pullrequest_count_service.rb View File

@@ -0,0 +1,41 @@
class Cache::PlatformPullrequestCountService < ApplicationService
attr_reader :increment_count

def initialize(increment_count=0)
@increment_count = increment_count
end

def call
set_platform_pullrequest_count

platform_pullrequest_count
end

def reset
reset_platform_pullrequest_count

platform_pullrequest_count
end

private

def platform_pullrequest_count_key
"platform-pullrequest-count"
end

def platform_pullrequest_count
$redis_cache.get(platform_pullrequest_count_key).to_i
end

def set_platform_pullrequest_count
if $redis_cache.exists(platform_pullrequest_count_key)
$redis_cache.incrby(platform_pullrequest_count_key, increment_count)
else
reset_platform_pullrequest_count
end
end

def reset_platform_pullrequest_count
$redis_cache.set(platform_pullrequest_count_key, PullRequest.count)
end
end

+ 43
- 0
app/services/cache/user_follow_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserFollowCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_follow_count

user_follow_count
end

def reset
reset_user_follow_count

user_follow_count
end

private

def user_follow_count_key
"user-follow-count-#{user.id}"
end

def user_follow_count
$redis_cache.get(user_follow_count_key).to_i
end

def set_user_follow_count
if $redis_cache.exists(user_follow_count_key)
$redis_cache.incrby(user_follow_count_key, increment_count)
else
reset_user_follow_count
end
end

def reset_user_follow_count
return if user.nil?
$redis_cache.set(user_follow_count_key, Watcher.where(watchable: user).count)
end
end

+ 43
- 0
app/services/cache/user_issue_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserIssueCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_issue_count

user_issue_count
end

def reset
reset_user_issue_count

user_issue_count
end

private

def user_issue_count_key
"user-issue-count-#{user.id}"
end

def user_issue_count
$redis_cache.get(user_issue_count_key).to_i
end

def set_user_issue_count
if $redis_cache.exists(user_issue_count_key)
$redis_cache.incrby(user_issue_count_key, increment_count)
else
reset_user_issue_count
end
end

def reset_user_issue_count
return if user.nil?
$redis_cache.set(user_issue_count_key, Issue.where(author_id: user.id).count)
end
end

+ 43
- 0
app/services/cache/user_project_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserProjectCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_project_count

user_project_count
end

def reset
reset_user_project_count

user_project_count
end

private

def user_project_count_key
"user-project-count-#{user.id}"
end

def user_project_count
$redis_cache.get(user_project_count_key).to_i
end

def set_user_project_count
if $redis_cache.exists(user_project_count_key)
$redis_cache.incrby(user_project_count_key, increment_count)
else
reset_user_project_count
end
end

def reset_user_project_count
return if user.nil?
$redis_cache.set(user_project_count_key, user.projects.count)
end
end

+ 43
- 0
app/services/cache/user_project_fork_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserProjectForkCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_project_fork_count

user_project_fork_count
end

def reset
reset_user_project_fork_count

user_project_fork_count
end

private

def user_project_fork_count_key
"user-project-fork-count-#{user.id}"
end

def user_project_fork_count
$redis_cache.get(user_project_fork_count_key).to_i
end

def set_user_project_fork_count
if $redis_cache.exists(user_project_fork_count_key)
$redis_cache.incrby(user_project_fork_count_key, increment_count)
else
reset_user_project_fork_count
end
end

def reset_user_project_fork_count
return if user.nil?
$redis_cache.set(user_project_fork_count_key, ForkUser.joins(:project).where(projects: {user_id: user.id}).count)
end
end

+ 60
- 0
app/services/cache/user_project_languages_count_service.rb View File

@@ -0,0 +1,60 @@
class Cache::UserProjectLanguagesCountService < ApplicationService
attr_reader :user, :key, :increment_count

def initialize(user, key=nil, increment_count=0)
@user = user
@key = key
@increment_count = increment_count
end

def call
set_user_project_language_count

user_project_language_count
end

def reset_by_key
reset_user_project_language_count_by_key

user_project_language_count
end

def reset
reset_user_project_language_count

user_project_language_count
end

private

def user_project_language_count_key
"user-project-language-count-#{user.id}"
end

def user_project_language_count
$redis_cache.hgetall(user_project_language_count_key).transform_values(&:to_i)
end

def set_user_project_language_count
if $redis_cache.hlen(user_project_language_count_key) == 0
reset_user_project_language_count
elsif $redis_cache.hget(user_project_language_count_key, key).nil?
reset_user_project_language_count_by_key
else
$redis_cache.hincrby(user_project_language_count_key, key, increment_count)
end
end

def reset_user_project_language_count_by_key
return if user.nil?
return if key.nil?
$redis_cache.hset(user_project_language_count_key, key, user.projects.joins(:project_language).where(project_languages: {name: key}).count)
end

def reset_user_project_language_count
return if user.nil?
user.projects.joins(:project_language).group("project_languages.name").count.each do |k, v|
$redis_cache.hset(user_project_language_count_key, k, v)
end
end
end

+ 43
- 0
app/services/cache/user_project_praises_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserProjectPraisesCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_project_praises_count

user_project_praises_count
end

def reset
reset_user_project_praises_count

user_project_praises_count
end

private

def user_project_praises_count_key
"user-project-praises-count-#{user.id}"
end

def user_project_praises_count
$redis_cache.get(user_project_praises_count_key).to_i
end

def set_user_project_praises_count
if $redis_cache.exists(user_project_praises_count_key)
$redis_cache.incrby(user_project_praises_count_key, increment_count)
else
reset_user_project_praises_count
end
end

def reset_user_project_praises_count
return if user.nil?
$redis_cache.set(user_project_praises_count_key, PraiseTread.where(praise_tread_object_type: 'Project', praise_tread_object_id: user.projects).count)
end
end

+ 43
- 0
app/services/cache/user_project_watchers_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserProjectWatchersCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_project_watchers_count

user_project_watchers_count
end

def reset
reset_user_project_watchers_count

user_project_watchers_count
end

private

def user_project_watchers_count_key
"user-project-watchers-count-#{user.id}"
end

def user_project_watchers_count
$redis_cache.get(user_project_watchers_count_key).to_i
end

def set_user_project_watchers_count
if $redis_cache.exists(user_project_watchers_count_key)
$redis_cache.incrby(user_project_watchers_count_key, increment_count)
else
reset_user_project_watchers_count
end
end

def reset_user_project_watchers_count
return if user.nil?
$redis_cache.set(user_project_watchers_count_key, Watcher.where(watchable_type: 'Project', watchable_id: user.projects).count)
end
end

+ 43
- 0
app/services/cache/user_pullrequest_count_service.rb View File

@@ -0,0 +1,43 @@
class Cache::UserPullrequestCountService < ApplicationService
attr_reader :user, :increment_count

def initialize(user, increment_count=0)
@user = user
@increment_count = increment_count
end

def call
set_user_pullrequest_count

user_pullrequest_count
end

def reset
reset_user_pullrequest_count

user_pullrequest_count
end

private

def user_pullrequest_count_key
"user-pullrequest-count-#{user.id}"
end

def user_pullrequest_count
$redis_cache.get(user_pullrequest_count_key).to_i
end

def set_user_pullrequest_count
if $redis_cache.exists(user_pullrequest_count_key)
$redis_cache.incrby(user_pullrequest_count_key, increment_count)
else
reset_user_pullrequest_count
end
end

def reset_user_pullrequest_count
return if user.nil?
$redis_cache.set(user_pullrequest_count_key, PullRequest.where(user_id: user.id).count)
end
end

+ 96
- 0
app/services/gitea/accelerator/base_service.rb View File

@@ -0,0 +1,96 @@
class Gitea::Accelerator::BaseService < ApplicationService

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

private
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

+ 2
- 95
app/services/gitea/accelerator/migrate_service.rb View File

@@ -1,4 +1,4 @@
class Gitea::Accelerator::MigrateService < ApplicationService
class Gitea::Accelerator::MigrateService < Gitea::Accelerator::BaseService
attr_reader :params

# params description:
@@ -36,8 +36,8 @@ class Gitea::Accelerator::MigrateService < ApplicationService
render_status(response)
end

private

private
def request_params
{
uid: access_uid,
@@ -52,97 +52,4 @@ class Gitea::Accelerator::MigrateService < ApplicationService
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

+ 31
- 0
app/services/gitea/accelerator/sync_mirrored_service.rb View File

@@ -0,0 +1,31 @@
# Sync a mirrored repository
class Gitea::Accelerator::SyncMirroredService < Gitea::Accelerator::BaseService
attr_reader :repo, :token

# repo *
# name of the repo to sync
# example:
# Gitea::Accelerator::SyncMirroredService.call(repo.identifier)
def initialize(repo, token=nil)
@repo = repo
@token = token
end

def call
return error('[gitea:] accelerator config missing') if check_accelerator!

response = post(url, request_params)
{status: response.status}
end

private

def request_params
Hash.new.merge(token: token).compact
end

def url
"/repos/#{access_username}/#{repo}/mirror-sync".freeze
end
end

+ 23
- 0
app/services/gitea/user/headmap_service.rb View File

@@ -0,0 +1,23 @@
class Gitea::User::HeadmapService < Gitea::ClientService
attr_reader :start_time, :end_time, :username

def initialize(username, start_time, end_time)
@username = username
@start_time = start_time
@end_time = end_time
end

def call
response = get(url, params)
render_response(response)
end

private
def params
Hash.new.merge(start: start_time, end: end_time)
end

def url
"/users/#{username}/heatmap".freeze
end
end

+ 61
- 0
app/services/projects/accept_join_service.rb View File

@@ -0,0 +1,61 @@
class Projects::AcceptJoinService < ApplicationService
attr_accessor :applied_project, :owner
attr_reader :user, :project

def initialize(user, applied_project)
@user = user
@project = applied_project.project
@applied_project = applied_project
end

def call
Rails.logger.info("###### Project accept_join_service begin ######")
ActiveRecord::Base.transaction do
validate!
update_apply
operate_project_member
send_apply_message
end

Rails.logger.info("##### Project accept_join_service end ######")


return @applied_project
end

private
def permission
case @applied_project.role
when 'manager'
'admin'
when 'developer'
'write'
when 'reporter'
'read'
else
'read'
end
end

def validate!
raise Error, '该申请已经被接受' if @applied_project.accepted?
raise Error, '该申请不存在' unless @applied_project.present?
raise Error, '未拥有接受申请权限' unless is_permit_operator
end

def is_permit_operator
return @user.admin? || @project.manager?(@user)
end

def update_apply
@applied_project.update!(status: 'accepted')
end

def operate_project_member
Projects::AddMemberInteractor.call(@project.owner, @project, @applied_project.user, permission)
end

def send_apply_message
SendJoinProjectAppliedMessageJob.perform_now(@applied_project, @user, 'successed')
end
end

+ 22
- 16
app/services/projects/apply_join_service.rb View File

@@ -9,32 +9,31 @@ class Projects::ApplyJoinService < ApplicationService
end

def call
validate!

# 项目报告人员直接加入项目
if params[:role] == 'reporter'
# Projects::JoinService.call(project, user, role: 'reporter')
return project
end
# if params[:role] == 'reporter'
# # Projects::JoinService.call(project, user, role: 'reporter')
# return project
# end

ActiveRecord::Base.transaction do
validate!
apply = user.applied_projects.create!(project: project, role: role_value)
apply
# apply.forge_activities.find_or_create_by!(user: user, project: project)

apply.forge_activities.find_or_create_by!(user: user, project: project)

notify_project_manager!(apply)
# notify_project_manager!(apply)
end

# notify_project_owner
ApplyJoinProjectNotifyJob.perform_later(user.id, project.id, role_value)
# ApplyJoinProjectNotifyJob.perform_later(user.id, project.id, role_value)

project
end

private

def project
@_project ||= Project.find_by(invite_code: params[:code].to_s.strip)
@_project ||= Project.find_by('binary(invite_code) = ? ',"#{params[:code].to_s.strip}")
end

def role_value
@@ -43,7 +42,8 @@ class Projects::ApplyJoinService < ApplicationService
when 'manager' then 3
when 'developer' then 4
when 'reporter' then 5
else raise Error, '角色无效'
else
5
end
end

@@ -74,12 +74,18 @@ class Projects::ApplyJoinService < ApplicationService
def validate!
# params check
raise Error, '邀请码不能为空' if params[:code].blank?
raise Error, '角色不能为空' if params[:role].blank?
raise Error, '角色无效' unless %w(manager developer reporter).include?(params[:role])
raise Error, '请输入6位项目邀请码' unless valid_invite_code( params[:code])

# logical check
raise Error, '邀请码无效' if project.blank?
raise Error, '您已在该项目中' if project.member?(user)
raise Error, '您已经提交过申请' if user.applied_projects.pending.exists?(project: project)
raise Error, '您已是项目成员' if project.member?(user)
raise Error, '您已经提交过申请' if user.applied_projects.common.exists?(project: project)
end

def valid_invite_code(str)
if (str =~ /^[A-Za-z0-9]{6}+$/)
return true
end
return false
end
end

+ 39
- 0
app/services/projects/refuse_join_service.rb View File

@@ -0,0 +1,39 @@
class Projects::RefuseJoinService < ApplicationService
attr_accessor :applied_project, :owner
attr_reader :user, :project

def initialize(user, applied_project)
@user = user
@project = applied_project.project
@applied_project = applied_project
end

def call
Rails.logger.info("###### Project refuse_join_service begin ######")
validate!
update_apply
send_apply_message
Rails.logger.info("###### Project refuse_join_service end ######")

return @applied_project
end

private
def validate!
raise Error, '该申请已被拒绝' if @applied_project.refused?
raise Error, '该申请不存在' unless @applied_project.present?
raise Error, '未拥有接受申请权限' unless is_permit_operator
end

def is_permit_operator
return @user.admin? || @project.manager?(@user)
end

def update_apply
@applied_project.update!(status: 'refused')
end

def send_apply_message
SendJoinProjectAppliedMessageJob.perform_now(@applied_project, @user, 'failure')
end
end

+ 1
- 1
app/services/projects/transfer_service.rb View File

@@ -34,7 +34,7 @@ class Projects::TransferService < ApplicationService
def update_visit_teams
if new_owner.is_a?(Organization)
# 为包含组织所有项目的团队创建项目访问权限
new_owner.build_permit_team_projects(project.id)
new_owner.build_permit_team_projects!(project.id)
else
project.team_projects.each(&:destroy!)
end


+ 4
- 1
app/views/admins/shared/_sidebar.html.erb View File

@@ -42,8 +42,11 @@
<% end %>
</li>
<li>
<%= sidebar_item('/admins/sidekiq', '定时任务', icon: 'bell', controller: 'root') %>
<%= sidebar_item('/admins/sidekiq', '定时任务', icon: 'bell', controller: 'root') %>
</li>
<%= render_admin_statistics_item %>

<li><%= sidebar_item('/', '返回主站', icon: 'sign-out', controller: 'root') %></li>
</ul>
</nav>

+ 19
- 0
app/views/applied_projects/_detail.json.jbuilder View File

@@ -0,0 +1,19 @@
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.id object.id
json.status object.status
json.role object.role
json.created_at format_time(object.created_at)
json.time_ago time_from_now(object.created_at)

+ 1
- 0
app/views/applied_projects/create.json.jbuilder View File

@@ -0,0 +1 @@
json.partial! "detail", locals: {object: @applied_project}

+ 6
- 0
app/views/issues/_simple_issue_item.json.jbuilder View File

@@ -1,4 +1,10 @@
json.name issue.try(:subject)
json.issue_type issue.issue_type == "1" ? "普通" : "悬赏"
json.status_id issue.try(:status_id)
json.issue_status issue.issue_status.try(:name)
json.priority issue.priority.try(:name)
json.priority_id issue.try(:priority_id)
json.version issue.version.try(:name)
json.created_at format_time(issue.try(:created_on))
json.updated_at format_time(issue.try(:updated_on))
json.assign_user_name issue&.get_assign_user.try(:show_real_name)


+ 17
- 0
app/views/project_trends/_detail.json.jbuilder View File

@@ -0,0 +1,17 @@
json.id trend.id
json.trend_type trend.trend_type
json.action_type l("trend.#{trend.action_type}") + l("trend.#{trend.trend_type}")
json.trend_id trend.trend_id
json.user_name trend.user.try(:show_real_name)
json.user_login trend.user.login
json.user_avatar url_to_avatar(trend.user)
json.action_time time_from_now(trend.created_at)

if trend.trend_type == "Issue"
json.partial! "issues/simple_issue_item", locals: {issue: trend.trend}
elsif trend.trend_type == "VersionRelease"
json.partial! "version_releases/simple_version_release", locals: {version: trend.trend}
else
json.name trend.trend&.title
json.created_at format_time(trend.trend&.created_at)
end

+ 2
- 18
app/views/project_trends/index.json.jbuilder View File

@@ -9,25 +9,9 @@ json.limit @limit
json.project_trends_size @project_trends_size
json.project_trends do
json.array! @project_trends.to_a.each do |trend|
json.id trend.id
json.trend_type trend.trend_type
json.action_type l("trend.#{trend.action_type}") + l("trend.#{trend.trend_type}")
json.trend_id trend.trend_id
json.user_name trend.user.try(:show_real_name)
json.user_login trend.user.login
json.user_avatar url_to_avatar(trend.user)

if trend.trend_type == "Issue"
json.partial! "issues/simple_issue_item", locals: {issue: trend.trend}
elsif trend.trend_type == "VersionRelease"
json.partial! "version_releases/simple_version_release", locals: {version: trend.trend}
else
json.name trend.trend.title
json.created_at format_time(trend.trend.created_at)
end

#后续需要天际pullrequest 和 版本的内容
json.partial! "detail", trend: trend
end
end


+ 4
- 0
app/views/projects/_project_detail.json.jbuilder View File

@@ -5,6 +5,9 @@ json.name project.name
json.description Nokogiri::HTML(project.description).text
json.visits project.visits
json.praises_count project.praises_count.to_i
json.watchers_count project.watchers_count.to_i
json.issues_count project.issues_count.to_i
json.pull_requests_count project.pull_requests_count.to_i
json.forked_count project.forked_count.to_i
json.is_public project.is_public
json.mirror_url project.repository&.mirror_url
@@ -14,6 +17,7 @@ json.time_ago time_from_now(project.updated_on)
json.forked_from_project_id project.forked_from_project_id
json.open_devops project.open_devops?
json.platform project.platform
json.is_pinned project.has_pinned_users.include?(current_user)
json.author do
if project.educoder?
project_educoder = project.project_educoder


+ 3
- 2
app/views/repositories/detail.json.jbuilder View File

@@ -7,12 +7,13 @@ else
json.readme @result[:readme].merge(content: readme_render_decode64_content(@result[:readme]["content"], nil))
end
json.identifier render_identifier(@project)
json.invite_code @project.invite_code
json.name @project.name
json.description @project.description
json.project_id @project.id
json.repo_id @repository.id
json.issues_count @project.issues_count.to_i - @project.pull_requests_count.to_i
json.pull_requests_count @project.pull_requests_count
json.pull_requests_count @project.pull_requests_count
json.project_identifier render_identifier(@project)
json.praises_count @project.praises_count.to_i
json.forked_count @project.forked_count.to_i
@@ -48,7 +49,7 @@ if @result[:repo]
json.size replace_bytes_to_b(number_to_human_size(@result[:repo]['size'].to_i*1024))
json.ssh_url @result[:repo]['ssh_url']
json.clone_url @result[:repo]['clone_url']
json.default_branch @result[:repo]['default_branch']
json.default_branch @project.educoder? ? "master" : @result[:repo]['default_branch']
json.empty @result[:repo]['empty']
json.full_name @result[:repo]['full_name']
json.private @result[:repo]['private']


+ 2
- 1
app/views/repositories/edit.json.jbuilder View File

@@ -12,4 +12,5 @@ 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
end
json.is_pinned @project.has_pinned_users.include?(current_user)

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save