diff --git a/.gitignore b/.gitignore index 2eac1d277..4a01bd5ec 100644 --- a/.gitignore +++ b/.gitignore @@ -81,7 +81,6 @@ db/bak/ docker/ educoder.sql redis_data/ -Dockerfile dump.rdb .tags* ceshi_user.xlsx diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..236e36935 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM ubuntu:18.04 + +RUN apt update + +RUN apt install -y openssl libssl-dev imagemagick git ruby-dev nodejs libmariadb-dev libmysqlclient-dev shared-mime-info libpq-dev libxml2-dev libxslt-dev +RUN DEBIAN_FRONTEND="noninteractive" apt -y install tzdata +RUN ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +WORKDIR /home/app/gitlink + +ADD ./ /home/app/gitlink + +RUN gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ + +RUN gem update --system + +RUN gem install bundler +RUN gem install rake + +RUN rm -rf Gemfile.lock + +#RUN cp config/configuration.yml.example config/configuration.yml +#RUN cp config/database.yml.example config/database.yml +#RUN touch config/redis.yml +#RUN touch config/elasticsearch.yml + +RUN bundle install + +EXPOSE 4000 +RUN rails s -p 4000 -b '0.0.0.0' + + + diff --git a/Gemfile b/Gemfile index e37371108..41c3e1394 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ -source 'https://gems.ruby-china.com' +#source 'https://gems.ruby-china.com' +source 'https://mirrors.cloud.tencent.com/rubygems/' git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'rails', '~> 5.2.0' @@ -98,9 +99,9 @@ gem 'font-awesome-sass', '4.7.0' gem 'rails-i18n', '~> 5.1' # job -gem 'sidekiq' +gem 'sidekiq',"5.2.8" gem 'sinatra' -gem "sidekiq-cron", "~> 1.1" +gem "sidekiq-cron", "1.2.0" # batch insert gem 'bulk_insert' @@ -139,4 +140,4 @@ gem 'doorkeeper' gem 'doorkeeper-jwt' -gem 'gitea-client', '~> 0.11.1' \ No newline at end of file +gem 'gitea-client', '~> 0.11.6' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index e27c504aa..ee0c909a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,8 @@ GEM activerecord (>= 3.1.0, < 7) diff-lcs (1.3) diffy (3.3.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) doorkeeper (5.5.1) railties (>= 5) doorkeeper-jwt (0.4.1) @@ -133,6 +135,8 @@ GEM fugit (1.4.1) et-orbi (~> 1.1, >= 1.1.8) raabro (~> 1.4) + gitea-client (0.10.5) + rest-client (~> 2.1.0) globalid (0.4.2) activesupport (>= 4.2.0) grape-entity (0.7.1) @@ -143,6 +147,9 @@ GEM harmonious_dictionary (0.0.1) hashie (3.6.0) htmlentities (4.3.4) + http-accept (1.7.0) + http-cookie (1.0.5) + domain_name (~> 0.5) i18n (1.8.2) concurrent-ruby (~> 1.0) io-like (0.3.1) @@ -180,6 +187,9 @@ GEM mimemagic (~> 0.3.2) maruku (0.7.3) method_source (0.9.2) + mime-types (3.4.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2022.0105) mimemagic (0.3.10) nokogiri (~> 1) rake @@ -193,6 +203,7 @@ GEM mustermann (1.1.1) ruby2_keywords (~> 0.0.1) mysql2 (0.5.3) + netrc (0.11.0) nio4r (2.5.2) nokogiri (1.10.8) mini_portile2 (~> 2.4.0) @@ -292,6 +303,11 @@ GEM regexp_parser (1.7.0) request_store (1.5.0) rack (>= 1.4) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) reverse_markdown (1.4.0) nokogiri roo (2.8.3) @@ -418,6 +434,9 @@ GEM thread_safe (~> 0.1) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) unicode-display_width (1.6.1) web-console (3.7.0) actionview (>= 5.0) @@ -459,6 +478,7 @@ DEPENDENCIES enumerize faraday (~> 0.15.4) font-awesome-sass (= 4.7.0) + gitea-client (~> 0.10.2) grape-entity (~> 0.7.1) groupdate (~> 4.1.0) harmonious_dictionary (~> 0.0.1) @@ -496,8 +516,8 @@ DEPENDENCIES sass-rails (~> 5.0) searchkick selenium-webdriver - sidekiq - sidekiq-cron (~> 1.1) + sidekiq (= 5.2.8) + sidekiq-cron (= 1.2.0) simple_form simple_xlsx_reader sinatra diff --git a/README.md b/README.md index c2988e289..2e84b704d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ GitLink(确实开源)是中国计算机学会(CCF)官方指定的开源 - **分布式协作开发**:基于Git打造分布式代码托管环境,提供免费公、私有代码仓库,支持在线文件编辑、代码分支管理、协作贡献统计、代码仓库复刻(Fork)、贡献合并请求(PR)、群智贡献审阅等功能,让您的项目在这里健康、快速的成长! -- **一站式过程管理**:提供易修(Issue)、里程碑、通知提醒、标签归档等多样化任务管理工具,支持各类开发任务的发布、指派与跟踪,同时提供在线Wiki文档、组织多粒度管理等功能,为您搭建一站式的项目过程管理环境,让您的团队协作更高效、过程更透明! +- **一站式过程管理**:提供疑修(Issue)、里程碑、通知提醒、标签归档等多样化任务管理工具,支持各类开发任务的发布、指派与跟踪,同时提供在线Wiki文档、组织多粒度管理等功能,为您搭建一站式的项目过程管理环境,让您的团队协作更高效、过程更透明! - **高效流水线运维**:融合DevOps思想,提供轻量级的工作流引擎(Engine),打通编码、测试、构建、部署等开发运维环节;支持自定义配置、代码静态扫描、构建自动触发、容器镜像托管等功能,同时支持接入第三方运维工具,让您的代码更加快速、可靠地形成高质量的产品! @@ -34,18 +34,52 @@ GitLink(确实开源)是中国计算机学会(CCF)官方指定的开源 * imagemagick ### 步骤 - -(1)克隆稳定版本 +(1)安装 Rails 必要的一些三方库: +- Mac OS X +```bash + brew install imagemagick ghostscript libxml2 libxslt libiconv ``` + +- Ubuntu +```bash +sudo apt-get update +sudo apt-get install -y openssl libssl-dev imagemagick git ruby-dev nodejs libmariadb-dev libmysqlclient-dev shared-mime-info libpq-dev libxml2-dev libxslt-dev +sudo DEBIAN_FRONTEND="noninteractive" apt-get install -y tzdata +sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +``` + +(2)安装 Ruby, Rails 运行环境:[如何快速正确的安装 Ruby, Rails 运行环境](https://ruby-china.org/wiki/install_ruby_guide) +```bash +#检验环境是否正确 +ruby -v +#ruby 2.4.x ... + +gem -v +#3.x.x + +bundle -v +#Bundler version 2.x.x + +rails -v +#Rails 5.2.x +``` + +(3)克隆稳定版本 +```bash git clone -b master https://gitlink.org.cn/Gitlink/forgeplus.git ``` -(2)安装依赖包 +(4)安装依赖包 ```bash -cd forgeplus && bundle install +#进入目录 +cd forgeplus +#删除Gemfile.lock +rm -rf Gemfile.lock +#安装依赖包 +bundle install ``` -(3)配置初始化文件:进入项目根目录执行以下命令 +(5)配置初始化文件:进入项目根目录执行以下命令 ```bash cp config/configuration.yml.example config/configuration.yml cp config/database.yml.example config/database.yml @@ -53,8 +87,8 @@ touch config/redis.yml touch config/elasticsearch.yml ``` -(4)配置数据库:数据库配置信息请查看/config/database.yml文件,项目默认采用mysql数据库, 如需更改,请自行修改配置信息,默认配置如下 -```bash +(6)配置数据库:数据库配置信息请查看/config/database.yml文件,项目默认采用mysql数据库, 如需更改,请自行修改配置信息,默认配置如下 +```yaml default: &default adapter: mysql2 host: 127.0.0.1 @@ -63,7 +97,7 @@ default: &default password: 123456 ``` -(5)配置gitea服务(可选):如需要部署自己的gitea平台,请参考[gitea官方平台文档](https://docs.gitea.io/zh-cn/install-from-binary/)。因目前gitea平台api受限,暂时推荐从forge平台获取[gitea部署文件](https://www.gitlink.org.cn/Gitlink/gitea-binary)进行部署 +(7)配置gitea服务(可选):如需要部署自己的gitea平台,请参考[gitea官方平台文档](https://docs.gitea.io/zh-cn/install-from-binary/)。因目前gitea平台api受限,暂时推荐从forge平台获取[gitea部署文件](https://www.gitlink.org.cn/Gitlink/gitea-binary)进行部署 - 配置gitea服务步骤: @@ -71,7 +105,7 @@ default: &default -- 修改forge平台的 config/configuration.yml中的gitea服务指向地址,如: -```ruby +```yaml gitea: access_key_id: 'root' access_key_secret: 'password' @@ -79,61 +113,59 @@ gitea: base_url: '/api/v1' ``` -(6)安装redis环境:请自行搜索各平台如何安装部署redis环境 +(8)配置/config/database.yml文件(安装redis环境:请自行搜索各平台如何安装部署redis环境) +```yaml +default: &default + url: redis://localhost:6379 + db: 1 -(7)安装imagemagick插件: -- Mac OS X -```bash - brew install imagemagick ghostscript +production: + <<: *default + url: redis://localhost:6379 ``` -- Linux -```bash -sudo apt-get install -y imagemagick -``` - -(8)创建数据库:开发环境为development, 生成环境为production +(9)创建数据库:开发环境为development, 生成环境为production ```bash rails db:create RAILS_ENV=development ``` -(9)导入数据表结构 +(10)导入数据表结构 ```bash bundle exec rake sync_table_structure:import_csv ``` -(10)执行migrate迁移文件:开发环境为development, 生成环境为production +(11)执行migrate迁移文件:开发环境为development, 生成环境为production ```bash rails db:migrate RAILS_ENV=development ``` -(11)clone前端代码:将前端代码克隆到public/react目录下,目录结构应该是: public/react/build +(12)clone前端代码:将前端代码克隆到public/react目录下,目录结构应该是: public/react/build ```bash -git clone -b standalone https://gitlink.org.cn/Gitlink/build.git +git clone -b master https://gitlink.org.cn/Gitlink/build.git ``` -(12)启动redis(此处以macOS系统为例) +(13)启动redis(此处以macOS系统为例) ```bash redis-server& ``` -(13)启动sidekiq:开发环境为development, 生成环境为production +(14)启动sidekiq:开发环境为development, 生成环境为production ```bash bundle exec sidekiq -C config/sidekiq.yml -e production -d ``` -(14)启动rails服务 +(15)启动rails服务 ```bash rails s ``` -(15)浏览器访问:在浏览器中输入如下地址访问 +(16)浏览器访问:在浏览器中输入如下地址访问 ```bash http://localhost:3000/ ``` -(16)其他说明:通过页面注册以第一个用户为平台管理员用户 +(17)其他说明:通过页面注册以第一个用户为平台管理员用户 ## 页面展示 diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 4a104129b..0d1660a0f 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -213,6 +213,7 @@ class AccountsController < ApplicationController def change_password @user = User.find_by(login: params[:login]) + return render_error("此用户禁止修改密码!") if @user.id.to_i === 104691 return render_error("未找到相关用户!") if @user.blank? return render_error("旧密码不正确") unless @user.check_password?(params[:old_password]) diff --git a/app/controllers/admins/projects_rank_controller.rb b/app/controllers/admins/projects_rank_controller.rb new file mode 100644 index 000000000..c4f968da4 --- /dev/null +++ b/app/controllers/admins/projects_rank_controller.rb @@ -0,0 +1,16 @@ +class Admins::ProjectsRankController < Admins::BaseController + + def index + @rank_date = rank_date + deleted_data = $redis_cache.smembers("v2-project-rank-deleted") + $redis_cache.zrem("v2-project-rank-#{rank_date}", deleted_data) unless deleted_data.blank? + @date_rank = $redis_cache.zrevrange("v2-project-rank-#{rank_date}", 0, -1, withscores: true) + end + + private + + def rank_date + params.fetch(:date, Date.today.to_s) + end + +end \ No newline at end of file diff --git a/app/controllers/admins/users_rank_controller.rb b/app/controllers/admins/users_rank_controller.rb new file mode 100644 index 000000000..2c7a62ae5 --- /dev/null +++ b/app/controllers/admins/users_rank_controller.rb @@ -0,0 +1,15 @@ +class Admins::UsersRankController < Admins::BaseController + + def index + @rank_date = rank_date + @date_rank = $redis_cache.zrevrange("v2-user-rank-#{rank_date}", 0, -1, withscores: true) + end + + private + + def rank_date + params.fetch(:date, Date.today.to_s) + end + + +end \ No newline at end of file diff --git a/app/controllers/api/v1/projects/branches_controller.rb b/app/controllers/api/v1/projects/branches_controller.rb index bc4919616..0c89f6012 100644 --- a/app/controllers/api/v1/projects/branches_controller.rb +++ b/app/controllers/api/v1/projects/branches_controller.rb @@ -1,16 +1,40 @@ class Api::V1::Projects::BranchesController < Api::V1::BaseController - before_action :require_public_and_member_above, only: [:all] + before_action :require_public_and_member_above, only: [:index, :all] + + def index + @result_object = Api::V1::Projects::Branches::ListService.call(@project, {name: params[:keyword], page: page, limit: limit}, current_user&.gitea_token) + end def all @result_object = Api::V1::Projects::Branches::AllListService.call(@project, current_user&.gitea_token) end - before_action :require_operate_above, only: [:create] + before_action :require_operate_above, only: [:create, :destroy] def create @result_object = Api::V1::Projects::Branches::CreateService.call(@project, branch_params, current_user&.gitea_token) end + def destroy + @result_object = Api::V1::Projects::Branches::DeleteService.call(@project, params[:name], current_user&.gitea_token) + if @result_object + return render_ok + else + return render_error('删除分支失败!') + end + end + + before_action :require_manager_above, only: [:update_default_branch] + + def update_default_branch + @result_object = Api::V1::Projects::Branches::UpdateDefaultBranchService.call(@project, params[:name], current_user&.gitea_token) + if @result_object + return render_ok + else + return render_error('更新默认分支失败!') + end + end + private def branch_params params.require(:branch).permit(:new_branch_name, :old_branch_name) diff --git a/app/controllers/api/v1/projects/tags_controller.rb b/app/controllers/api/v1/projects/tags_controller.rb new file mode 100644 index 000000000..06c3b1c8e --- /dev/null +++ b/app/controllers/api/v1/projects/tags_controller.rb @@ -0,0 +1,19 @@ +class Api::V1::Projects::TagsController < Api::V1::BaseController + before_action :require_public_and_member_above, only: [:index] + + def index + @release_tags = @repository.version_releases.pluck(:tag_name) + @result_object = Api::V1::Projects::Tags::ListService.call(@project, {page: page, limit: limit}, current_user&.gitea_token) + end + + before_action :require_operate_above, only: [:destroy] + + def destroy + @result_object = Api::V1::Projects::Tags::DeleteService.call(@project, params[:name], current_user&.gitea_token) + if @result_object + return render_ok + else + return render_error('删除标签失败!') + end + end +end \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7490cb27f..8129df8f1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,6 @@ require 'oauth2' +# require 'openssl' +# require 'jwt' class ApplicationController < ActionController::Base include CodeExample @@ -295,17 +297,24 @@ class ApplicationController < ActionController::Base # Find the current user #Rails.logger.info("current_laboratory is #{current_laboratory} domain is #{request.subdomain}") if request.headers["Authorization"].present? && request.headers["Authorization"].start_with?('Bearer') - tip_exception(401, "请登录后再操作!") unless valid_doorkeeper_token? - if @doorkeeper_token.present? - # client方法对接,需要一直带着用户标识uid - if @doorkeeper_token.resource_owner_id.blank? - tip_exception(-1, "缺少用户标识!") if params[:uid].nil? - User.current = User.find(params[:uid]) - else - User.current = User.find_by(id: @doorkeeper_token.resource_owner_id) + if !valid_doorkeeper_token? + header = request.authorization + pattern = /^Bearer /i + token = header.gsub(pattern, "") + User.current, message = Bot.decode_jwt_token(token) + tip_exception(401, message) if message.present? + else + if @doorkeeper_token.present? + # client方法对接,需要一直带着用户标识uid + if @doorkeeper_token.resource_owner_id.blank? + tip_exception(-1, "缺少用户标识!") if params[:uid].nil? + User.current = User.find(params[:uid]) + else + User.current = User.find_by(id: @doorkeeper_token.resource_owner_id) + end end end - else + else User.current = find_current_user uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous")) diff --git a/app/controllers/concerns/api/project_helper.rb b/app/controllers/concerns/api/project_helper.rb index 52967e784..44cac08c7 100644 --- a/app/controllers/concerns/api/project_helper.rb +++ b/app/controllers/concerns/api/project_helper.rb @@ -6,7 +6,8 @@ module Api::ProjectHelper repo = params[:repo] @project, @owner = Project.find_with_namespace(namespace, repo) - + @repository = @project&.repository + if @project logger.info "###########:project founded" @project diff --git a/app/controllers/installations_controller.rb b/app/controllers/installations_controller.rb new file mode 100644 index 000000000..20dd25e40 --- /dev/null +++ b/app/controllers/installations_controller.rb @@ -0,0 +1,67 @@ +class InstallationsController < ApplicationController + include RegisterHelper + before_action :require_login + + def index + @install_bots = BotInstall.where(:installer_id => current_user.id) + end + + def update_secret + ActiveRecord::Base.transaction do + bot = Bot.find params[:id] + application = Doorkeeper::Application.find_by(uid: bot.client_id, secret: bot.client_secret) + bot.client_secret = Doorkeeper::OAuth::Helpers::UniqueToken.generate + bot.save! + application.secret = bot.client_secret + application.save! + render_ok + end + end + + def update_private_key + bot = Bot.find params[:id] + bot.private_key = OpenSSL::PKey::RSA::generate(2048).to_s + bot.save! + render_ok + end + + def auth_active + begin + @bot = Bot.find params[:id] + tip_exception("该Bot已激活") if Doorkeeper::Application.find_by(uid: @bot.client_id, secret: @bot.client_secret).present? + @bot.client_id = Doorkeeper::OAuth::Helpers::UniqueToken.generate if params[:client_id].blank? + @bot.client_secret = Doorkeeper::OAuth::Helpers::UniqueToken.generate if params[:client_secret].blank? + @bot.private_key = OpenSSL::PKey::RSA::generate(2048).to_s + @bot.owner_id = current_user.id + ActiveRecord::Base.transaction do + # 注册bot对应oauth应用 + Doorkeeper::Application.create!(name: @bot.name, uid: @bot.client_id, secret: @bot.client_secret, redirect_uri: "https://gitlink.org.cn") + # 注册bot对应用户 + result = autologin_register(User.generate_user_login('b'), nil, "#{SecureRandom.hex(6)}", 'bot', nickname: @bot.name) + tip_exception(-1, result[:message]) if result[:message].present? + @bot.uid = result[:user][:id] + @bot.save + render_ok + end + rescue Exception => e + tip_exception(-1, e.message) + end + end + + def access_tokens + @install_bot = BotInstall.find params[:id] + @bot = @install_bot.bot + @application = Doorkeeper::Application.find_by(uid: @bot.client_id, secret: @bot.client_secret) + tip_exception("该Bot未激活") if @application.blank? + # 给bot生成token,因为bot是机器人操作 + @access_token = Doorkeeper::AccessToken.create!({ :application_id => @application.id, + :resource_owner_id => @bot.uid, + :scopes => "public write", + :expires_in => "604800", + :use_refresh_token => true + }) + render_ok(token: @access_token.token) + end + +end + diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index ddb0facdf..efc7d3ea4 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -154,6 +154,8 @@ class IssuesController < ApplicationController Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" AtmeService.call(current_user, @atme_receivers, @issue) if @atme_receivers.size > 0 + # 新增时向grimoirelab推送事件 + IssueWebhookJob.set(wait: 5.seconds).perform_later(@issue.id) render json: {status: 0, message: "创建成功", id: @issue.id} else diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 53f6d1aea..52c776477 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -53,7 +53,7 @@ class ProjectsController < ApplicationController ActiveRecord::Base.transaction do Projects::CreateForm.new(project_params).validate! @project = Projects::CreateService.new(current_user, project_params).call - OpenProjectDevOpsJob.perform_later(@project&.id, current_user.id) + OpenProjectDevOpsJob.set(wait: 5.seconds).perform_later(@project&.id, current_user.id) end rescue Exception => e uid_logger_error(e.message) diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 69491282d..c98174777 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -213,5 +213,12 @@ module RepositoriesHelper def tmp_dir "repo" end - + + def repo_git_submodule_url(owner, repo, path) + unless (path.starts_with?('http://') || path.starts_with?('https://')) + path = File.expand_path(path, "/#{owner&.login}/#{repo&.identifier}") + end + + return path + end end diff --git a/app/jobs/issue_webhook_job.rb b/app/jobs/issue_webhook_job.rb new file mode 100644 index 000000000..4027a55b2 --- /dev/null +++ b/app/jobs/issue_webhook_job.rb @@ -0,0 +1,186 @@ +class IssueWebhookJob < ApplicationJob + include ApplicationHelper + queue_as :message + + def perform(issue_id) + issue = Issue.find_by(id: issue_id) + return if issue.blank? + project = issue.project + user = issue.user + domain = "#{Rails.application.config_for(:configuration)['platform_url']}" + gitea_domain = GiteaService.gitea_config[:domain] + ssh_url = "git@#{gitea_domain.gsub("https://", "")}:#{project.owner.login}/#{project.identifier}.git" + event_json = { + "action": "opened", + "number": 1, + "issue": { + "id": issue.id, + "url": "#{domain}/api/v1/#{project.owner.login}/#{project.identifier}/issues/#{issue.id}", + "html_url": "#{domain}/#{project.owner.login}/#{project.identifier}/issues/#{issue.id}", + "number": 1, + "user": { + "id": user.id, + "login": "#{user.login}", + "full_name": "", + "email": "#{user.mail}", + "avatar_url": "#{domain}/#{url_to_avatar(user)}", + "language": "", + "is_admin": false, + "last_login": "#{user.last_login_on}", + "created": "#{user.created_on}", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "#{user.login}" + }, + "original_author": "", + "original_author_id": 0, + "title": "#{issue.subject}", + "body": "#{issue.description}", + "ref": "", + "labels": [], + "milestone": nil, + "assignee": nil, + "assignees": nil, + "state": "open", + "is_locked": false, + "comments": 0, + "created_at": "#{issue.created_on}", + "updated_at": "#{issue.updated_on}", + "closed_at": nil, + "due_date": nil, + "pull_request": nil, + "repository": { + "id": 11307, + "name": "#{project.identifier}", + "owner": "#{project.owner.login}", + "full_name": "#{project.owner.login}/#{project.identifier}" + } + }, + "repository": { + "id": project.id, + "owner": { + "id": project.owner.id, + "login": "#{project.owner.login}", + "full_name": "", + "email": "#{project.owner.mail}", + "avatar_url": "#{domain}/#{url_to_avatar(project.owner)}", + "language": "", + "is_admin": false, + "last_login": "#{project.owner.last_login_on}", + "created": "#{project.owner.created_on}", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "#{project.owner.login}" + }, + "name": "#{project.identifier}", + "full_name": "#{project.owner.login}/#{project.identifier}", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": nil, + "mirror": false, + "size": 843, + "html_url": "#{domain}/#{project.owner.login}/#{project.identifier}", + "ssh_url": "#{ssh_url}", + "clone_url": "#{domain}/#{project.owner.login}/#{project.identifier}.git", + "original_url": "#{project.repository.mirror_url}", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 1, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "master", + "archived": false, + "created_at": "#{project.created_on}", + "updated_at": "#{project.updated_on}", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "has_projects": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "default_merge_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "" + }, + "sender": { + "id": user.id, + "login": "#{user.login}", + "full_name": "", + "email": "#{user.mail}", + "avatar_url": "#{domain}/#{url_to_avatar(user)}", + "language": "", + "is_admin": false, + "last_login": "#{user.last_login_on}", + "created": "#{user.created_on}", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "#{user.login}" + } + } + # puts "event_json:#{event_json.to_json}" + begin + url = URI("http://117.50.185.50:80") + http = Net::HTTP.new(url.host, url.port) + request = Net::HTTP::Post.new(url) + request["Content-Type"] = "application/json" + uuid = SecureRandom.uuid + request['X-GitHub-Delivery'] = uuid + request['X-Gitea-Delivery'] = uuid + request['X-Gogs-Delivery'] = uuid + request['X-GitHub-Event'] = 'issues' + request['X-Gitea-Event'] = 'issues' + request['X-Gogs-Event'] = 'issues' + request.body = JSON.dump(event_json) + + response = http.request(request) + Rails.logger.info "issue #{issue_id} webhook event======#{response.read_body}" + rescue Exception => e + Rails.logger.info "issue #{issue_id} webhook event error======#{e.message}" + puts "issue: #{issue_id} webhook event error======#{e.message}" + end + end + +end \ No newline at end of file diff --git a/app/jobs/migrate_remote_repository_job.rb b/app/jobs/migrate_remote_repository_job.rb index 5e56901a0..696607ae3 100644 --- a/app/jobs/migrate_remote_repository_job.rb +++ b/app/jobs/migrate_remote_repository_job.rb @@ -15,7 +15,7 @@ class MigrateRemoteRepositoryJob < ApplicationJob ## open jianmu devops project_id = repo&.project&.id puts "############ mirror project_id,user_id: #{project_id},#{user_id} ############" - OpenProjectDevOpsJob.perform_later(project_id, user_id) if project_id.present? && user_id.present? + OpenProjectDevOpsJob.set(wait: 5.seconds).perform_later(project_id, user_id) if project_id.present? && user_id.present? puts "############ mirror status: #{repo.mirror.status} ############" else repo&.mirror&.failed! diff --git a/app/models/bot.rb b/app/models/bot.rb new file mode 100644 index 000000000..2098ac1e2 --- /dev/null +++ b/app/models/bot.rb @@ -0,0 +1,51 @@ +# == Schema Information +# +# Table name: bot +# +# id :integer not null, primary key +# bot_name :string(255) +# bot_des :text(4294967295) +# webhook :string(255) +# is_public :integer +# logo :string(255) +# state :integer +# client_id :string(255) +# client_secret :string(255) +# web_url :string(255) +# category :string(255) +# install_num :integer default("0") +# update_time :datetime not null +# create_time :datetime not null +# private_key :text(65535) +# uid :integer +# owner_id :integer +# +# Indexes +# +# name (bot_name) UNIQUE +# + +class Bot < ApplicationRecord + self.table_name = "bot" + + has_many :install_bots + + def name + self.bot_name + end + + def self.decode_jwt_token(token) + decoded_token = JWT.decode token, nil, false + return [nil, "Token已过期"] if Time.now.to_i - 60 - decoded_token[0]["exp"].to_i > 0 + bot = Bot.find_by(id: decoded_token[0]["iss"]) + return [nil, "Token不存在"] if bot.blank? + rsa_private = OpenSSL::PKey::RSA.new(bot.private_key) + rsa_public = rsa_private.public_key + JWT.decode token, rsa_public, true, { algorithm: 'RS256' } + [User.find_by(id: bot.owner_id), ""] + rescue JWT::DecodeError + Rails.logger.error "jwt token decode error:#{token}" + [nil, "无效Token"] + end + +end diff --git a/app/models/bot_install.rb b/app/models/bot_install.rb new file mode 100644 index 000000000..d5283c131 --- /dev/null +++ b/app/models/bot_install.rb @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: install_bot +# +# id :integer not null, primary key +# bot_id :integer not null +# installer_id :integer not null +# store_id :integer not null +# state :integer not null +# create_time :datetime not null +# update_time :datetime not null +# + +# frozen_string_literal: true + +class BotInstall < ApplicationRecord + self.table_name = "install_bot" + belongs_to :bot + +end diff --git a/app/models/project.rb b/app/models/project.rb index a8816bd76..e1670bf53 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -188,7 +188,7 @@ class Project < ApplicationRecord forked_project = self.forked_from_project if forked_project.present? forked_project.decrement(:forked_count, 1) - forked_project.update_column(:forked_count, forked_project.forked_count) + forked_project.update_attribute(:forked_count, forked_project.forked_count) end end diff --git a/app/models/user.rb b/app/models/user.rb index df35f3a2f..b43e289ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -114,7 +114,7 @@ class User < Owner # trustie: 来自Trustie平台 # forge: 平台本身注册的用户 # military: 军科的用户 - enumerize :platform, in: [:forge, :educoder, :trustie, :military, :github, :gitee, :qq, :wechat], default: :forge, scope: :shallow + enumerize :platform, in: [:forge, :educoder, :trustie, :military, :github, :gitee, :qq, :wechat, :bot], default: :forge, scope: :shallow belongs_to :laboratory, optional: true has_one :user_extension, dependent: :destroy diff --git a/app/queries/projects/list_query.rb b/app/queries/projects/list_query.rb index 447ab5070..6b0843540 100644 --- a/app/queries/projects/list_query.rb +++ b/app/queries/projects/list_query.rb @@ -11,21 +11,19 @@ class Projects::ListQuery < ApplicationQuery end def call - collection = Project.visible - # 增加私有组织中项目过滤 - collection = collection.joins("left join organization_extensions on organization_extensions.organization_id = projects.user_id") - .where("organization_extensions.visibility is null or organization_extensions.visibility in (0,1)") - .where("projects.user_id > 0") + collection = main_collection collection = filter_projects(collection) sort = params[:sort_by] || "updated_on" sort_direction = params[:sort_direction] || "desc" collection = optimize_sorting(collection, sort) if params[:category_id].present? - custom_sort(collection, sort, sort_direction) - - # scope = scope.reorder("projects.#{sort} #{sort_direction}") - # scope + # 如果有搜索关键字根据ES搜索结果排序 + if params[:search].present? && @ids.present? + collection.reorder(Arel.sql("FIELD(projects.id,#{@ids.join(',')})")) + else + custom_sort(collection, sort, sort_direction) + end end def filter_projects(collection) @@ -37,15 +35,25 @@ class Projects::ListQuery < ApplicationQuery collection end + def main_collection + collection = Project.visible + # 增加私有组织中项目过滤 + collection = collection.joins("left join organization_extensions on organization_extensions.organization_id = projects.user_id") + .where("organization_extensions.visibility is null or organization_extensions.visibility in (0,1)") + .where("projects.user_id > 0") + collection + end + def by_search(items) - ids = Projects::ElasticsearchService.call(params[:search]) + @ids = Projects::ElasticsearchService.call(params[:search]) items = items.where(platform: 'forge') - if ids.present? - items = items.where(id: ids).by_name_or_identifier(params[:search]) + if @ids.present? + # items = items.where(id: @ids).by_name_or_identifier(params[:search]) + items = items.where(id: @ids) else - items = items.by_name_or_identifier(params[:search]) + items = items.by_name_or_identifier(params[:search]).or(main_collection.where(user_id: Owner.like(params[:search]).pluck(:id))) end - items.or(items.where(user_id: Owner.like(params[:search]).pluck(:id))) + items end def by_project_type(items) @@ -78,5 +86,14 @@ class Projects::ListQuery < ApplicationQuery relations end end - + + def by_recommend(items) + params[:recommend].to_s == "true" ? items.where(recommend: true) : items + end + + def exclude_fork(items) + return items if params[:exclude_forked].blank? + params[:exclude_forked].to_s == "true" ? items.where("forked_from_project_id is null") : items.where("forked_from_project_id is not null") + end + end diff --git a/app/services/api/v1/projects/branches/create_service.rb b/app/services/api/v1/projects/branches/create_service.rb index 2837dfcf4..eae3779f8 100644 --- a/app/services/api/v1/projects/branches/create_service.rb +++ b/app/services/api/v1/projects/branches/create_service.rb @@ -18,7 +18,7 @@ class Api::V1::Projects::Branches::CreateService < ApplicationService def call raise Error, errors.full_messages.join(",") unless valid? - check_new_branch_exist + check_branch_exist excute_data_to_gitea gitea_data @@ -43,9 +43,10 @@ class Api::V1::Projects::Branches::CreateService < ApplicationService raise Error, '创建分支失败!' unless @gitea_data.is_a?(Hash) end - def check_new_branch_exist + def check_branch_exist result = $gitea_client.get_repos_branch_name_set_by_owner_repo(owner, repo, {query: request_params}) rescue nil raise Error, '查询分支名称失败!' unless result.is_a?(Hash) - raise Error, '分支已存在!' if result['branch_name'].include?(@new_branch_name) + raise Error, '旧分支不存在!' if !result['branch_name'].include?(@old_branch_name) + raise Error, '新分支已存在!' if result['branch_name'].include?(@new_branch_name) end end \ No newline at end of file diff --git a/app/services/api/v1/projects/branches/delete_service.rb b/app/services/api/v1/projects/branches/delete_service.rb new file mode 100644 index 000000000..79a6ba0db --- /dev/null +++ b/app/services/api/v1/projects/branches/delete_service.rb @@ -0,0 +1,47 @@ +class Api::V1::Projects::Branches::DeleteService < ApplicationService + include ActiveModel::Model + + attr_accessor :project, :token, :owner, :repo, :branch_name + attr_accessor :gitea_data + + validates :branch_name, presence: true + + def initialize(project, branch_name, token=nil) + @project = project + @owner = project&.owner.login + @repo = project&.identifier + @branch_name = branch_name + @token = token + end + + def call + raise Error, errors.full_messages.join(",") unless valid? + + check_branch_exist + excute_data_to_gitea + + true + end + + private + def request_params + { + access_token: token + } + end + + def excute_data_to_gitea + begin + @gitea_data = $gitea_client.delete_repos_branches_by_owner_repo_branch(owner, repo, branch_name, {query: request_params}) + rescue => e + raise Error, '保护分支无法删除!' if e.to_s.include?("branch protected") + raise Error, '删除分支失败!' + end + end + + def check_branch_exist + result = $gitea_client.get_repos_branch_name_set_by_owner_repo(owner, repo, {query: request_params}) rescue nil + raise Error, '查询分支名称失败!' unless result.is_a?(Hash) + raise Error, '分支不存在!' if !result['branch_name'].include?(@branch_name) + end +end \ No newline at end of file diff --git a/app/services/api/v1/projects/branches/list_service.rb b/app/services/api/v1/projects/branches/list_service.rb new file mode 100644 index 000000000..e5c6fe442 --- /dev/null +++ b/app/services/api/v1/projects/branches/list_service.rb @@ -0,0 +1,40 @@ +class Api::V1::Projects::Branches::ListService < ApplicationService + + attr_accessor :project, :token, :owner, :repo, :name, :page, :limit + attr_accessor :gitea_data + + def initialize(project, params, token=nil) + @project = project + @owner = project&.owner.login + @repo = project&.identifier + @token = token + @name = params[:name] + @page = params[:page] + @limit = params[:limit] + end + + def call + load_gitea_data + + gitea_data + end + + private + def request_params + params = { + access_token: token, + page: page, + limit: limit + } + params.merge!({name: name}) if name.present? + + params + end + + def load_gitea_data + puts request_params + @gitea_data = $gitea_client.get_repos_branches_by_owner_repo(owner, repo, {query: request_params}) rescue nil + puts @gitea_data + raise Error, '获取分支列表失败!' unless @gitea_data.is_a?(Hash) + end +end \ No newline at end of file diff --git a/app/services/api/v1/projects/branches/update_default_branch_service.rb b/app/services/api/v1/projects/branches/update_default_branch_service.rb new file mode 100644 index 000000000..5c220aa5d --- /dev/null +++ b/app/services/api/v1/projects/branches/update_default_branch_service.rb @@ -0,0 +1,55 @@ +class Api::V1::Projects::Branches::UpdateDefaultBranchService < ApplicationService + include ActiveModel::Model + + attr_accessor :project, :token, :owner, :repo, :new_default_branch + attr_accessor :gitea_data + + validates :new_default_branch, presence: true + + def initialize(project, new_default_branch, token=nil) + @project = project + @owner = project&.owner.login + @repo = project&.identifier + @new_default_branch = new_default_branch + @token = token + end + + def call + raise Error, errors.full_messages.join(",") unless valid? + + check_branch_exist + update_project + excute_data_to_gitea + + gitea_data + end + + private + def request_params + { + access_token: token + } + end + + def request_body + { + default_branch: new_default_branch + } + end + + def update_project + @project.attributes = request_body + raise Error, '更新默认分支失败!' unless @project.save + end + + def excute_data_to_gitea + @gitea_data = $gitea_client.patch_repos_by_owner_repo(owner, repo, {body: request_body.to_json, query: request_params}) rescue nil + raise Error, '更新默认分支失败!' unless @gitea_data.is_a?(Hash) + end + + def check_branch_exist + result = $gitea_client.get_repos_branch_name_set_by_owner_repo(owner, repo, {query: request_params}) rescue nil + raise Error, '查询分支名称失败!' unless result.is_a?(Hash) + raise Error, '新默认分支不存在!' if !result['branch_name'].include?(@new_default_branch) + end +end \ No newline at end of file diff --git a/app/services/api/v1/projects/code_stats/list_service.rb b/app/services/api/v1/projects/code_stats/list_service.rb index 84f4bac36..a5e330e21 100644 --- a/app/services/api/v1/projects/code_stats/list_service.rb +++ b/app/services/api/v1/projects/code_stats/list_service.rb @@ -1,6 +1,6 @@ class Api::V1::Projects::CodeStats::ListService < ApplicationService - attr_reader :project, :ref, :owner, :repo, :token + attr_reader :project, :ref, :owner, :repo, :token, :page, :limit attr_accessor :gitea_data def initialize(project, params, token=nil) diff --git a/app/services/api/v1/projects/tags/delete_service.rb b/app/services/api/v1/projects/tags/delete_service.rb new file mode 100644 index 000000000..492898b53 --- /dev/null +++ b/app/services/api/v1/projects/tags/delete_service.rb @@ -0,0 +1,47 @@ +class Api::V1::Projects::Tags::DeleteService < ApplicationService + include ActiveModel::Model + + attr_accessor :project, :token, :owner, :repo, :tag_name + attr_accessor :gitea_data + + validates :tag_name, presence: true + + def initialize(project, tag_name, token=nil) + @project = project + @owner = project&.owner.login + @repo = project&.identifier + @tag_name = tag_name + @token = token + end + + def call + raise Error, errors.full_messages.join(",") unless valid? + + check_tag_exist + excute_data_to_gitea + + true + end + + private + def request_params + { + access_token: token + } + end + + def excute_data_to_gitea + begin + @gitea_data = $gitea_client.delete_repos_tags_by_owner_repo_tag(owner, repo, tag_name, {query: request_params}) + rescue => e + raise Error, '请先删除发行版!' if e.to_s.include?("409") + raise Error, '删除标签失败!' + end + end + + def check_tag_exist + result = $gitea_client.get_repos_tag_name_set_by_owner_repo(owner, repo, {query: request_params}) rescue nil + raise Error, '查询标签名称失败!' unless result.is_a?(Array) + raise Error, '标签不存在!' if !result.include?(@tag_name) + end +end \ No newline at end of file diff --git a/app/services/api/v1/projects/tags/list_service.rb b/app/services/api/v1/projects/tags/list_service.rb new file mode 100644 index 000000000..a7743fe00 --- /dev/null +++ b/app/services/api/v1/projects/tags/list_service.rb @@ -0,0 +1,36 @@ +class Api::V1::Projects::Tags::ListService < ApplicationService + + attr_accessor :project, :token, :owner, :repo, :page, :limit + attr_accessor :gitea_data + + def initialize(project, params, token=nil) + @project = project + @owner = project&.owner.login + @repo = project&.identifier + @token = token + @page = params[:page] + @limit = params[:limit] + end + + def call + load_gitea_data + + gitea_data + end + + private + def request_params + params = { + access_token: token, + page: page, + limit: limit + } + + params + end + + def load_gitea_data + @gitea_data = $gitea_client.get_repos_tags_by_owner_repo(owner, repo, {query: request_params}) rescue nil + raise Error, '获取标签列表失败!' unless @gitea_data.is_a?(Hash) + end +end \ No newline at end of file diff --git a/app/services/api/v1/users/update_email_service.rb b/app/services/api/v1/users/update_email_service.rb index bd1b5723c..9418cac6f 100644 --- a/app/services/api/v1/users/update_email_service.rb +++ b/app/services/api/v1/users/update_email_service.rb @@ -18,6 +18,7 @@ class Api::V1::Users::UpdateEmailService < ApplicationService end def call + raise Error, "此用户禁止修改邮箱." if @user.id.to_i === 104691 raise Error, errors.full_messages.join(",") unless valid? raise Error, "密码不正确." unless @user.check_password?(@password) exist_owner = Owner.find_by(mail: @mail) @@ -31,6 +32,7 @@ class Api::V1::Users::UpdateEmailService < ApplicationService change_user_email excute_data_to_gitea excute_change_email_from_gitea + remove_old_cache_for_user end return gitea_data @@ -68,4 +70,8 @@ class Api::V1::Users::UpdateEmailService < ApplicationService $gitea_client.delete_user_emails({body: {emails: [@old_mail]}.to_json, query: request_params}) $gitea_client.post_user_emails({body: {emails: [@mail]}.to_json, query: request_params}) end + + def remove_old_cache_for_user + $redis_cache.del("v2-owner-common:#{@user.login}-#{@old_mail}") + end end \ No newline at end of file diff --git a/app/services/api/v1/users/update_phone_service.rb b/app/services/api/v1/users/update_phone_service.rb index e41178e3d..ed53d7eb5 100644 --- a/app/services/api/v1/users/update_phone_service.rb +++ b/app/services/api/v1/users/update_phone_service.rb @@ -15,6 +15,7 @@ class Api::V1::Users::UpdatePhoneService < ApplicationService end def call + raise Error, "此用户禁止修改手机号." if @user.id.to_i === 104691 raise Error, errors.full_messages.join(",") unless valid? raise Error, "密码不正确." unless @user.check_password?(@password) exist_owner = Owner.find_by(phone: @phone) diff --git a/app/services/cache/v2/project_date_rank_service.rb b/app/services/cache/v2/project_date_rank_service.rb index 9d8ffca90..9df69bbb4 100644 --- a/app/services/cache/v2/project_date_rank_service.rb +++ b/app/services/cache/v2/project_date_rank_service.rb @@ -28,6 +28,10 @@ class Cache::V2::ProjectDateRankService < ApplicationService "v2-project-rank-#{@rank_date.to_s}" end + def project_rank_statistic_key + "v2-project-statistic:#{@project_id}-#{@rank_date.to_s}" + end + def project_rank $redis_cache.zscore(project_rank_key, @project_id) end @@ -35,26 +39,37 @@ class Cache::V2::ProjectDateRankService < ApplicationService def set_project_rank if @visits.present? $redis_cache.zincrby(project_rank_key, @visits.to_i * 1, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "visits", @visits.to_i) end if @watchers.present? $redis_cache.zincrby(project_rank_key, @watchers.to_i * 5, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "watchers", @watchers.to_i) end if @praises.present? $redis_cache.zincrby(project_rank_key, @praises.to_i * 5, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "praises", @praises.to_i) end if @forks.present? $redis_cache.zincrby(project_rank_key, @forks.to_i * 10, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "forks", @forks.to_i) end if @issues.present? $redis_cache.zincrby(project_rank_key, @issues.to_i * 5, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "issues", @issues.to_i) end if @pullrequests.present? $redis_cache.zincrby(project_rank_key, @pullrequests.to_i * 10, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "pullrequests", @pullrequests.to_i) end if @commits.present? $redis_cache.zincrby(project_rank_key, @commits.to_i * 5, @project_id) + $redis_cache.hincrby(project_rank_statistic_key, "commits", @commits.to_i) end $redis_cache.zscore(project_rank_key, @project_id) + + # 设置过期时间(一个月) + $redis_cache.expireat(project_rank_key, (@rank_date+30.days).to_time.to_i) + $redis_cache.expireat(project_rank_statistic_key, (@rank_date+30.days).to_time.to_i) end end \ No newline at end of file diff --git a/app/services/cache/v2/user_date_rank_service.rb b/app/services/cache/v2/user_date_rank_service.rb index a3351b724..b669e4d67 100644 --- a/app/services/cache/v2/user_date_rank_service.rb +++ b/app/services/cache/v2/user_date_rank_service.rb @@ -115,5 +115,9 @@ class Cache::V2::UserDateRankService < ApplicationService $redis_cache.zadd(user_rank_key, score-300, @user_id) if score > 300 $redis_cache.zscore(user_rank_key, @user_id) + + # 设置过期时间(一个月) + $redis_cache.expireat(user_rank_key, (@rank_date+30.days).to_time.to_i) + $redis_cache.expireat(user_date_statistic_key, (@rank_date+30.days).to_time.to_i) end end \ No newline at end of file diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 837775d36..f8b38df47 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -29,7 +29,7 @@ class Projects::ForkService < ApplicationService ProjectUnit.init_types(clone_project.id) - @project.update_column('forked_count', @project&.forked_count.to_i + 1) + @project.update_attribute('forked_count', @project&.forked_count.to_i + 1) new_repository.update_column('url', result['clone_url']) if result ForkUser.create(project_id: @project.id, fork_project_id: clone_project.id, user_id: clone_project.user_id) diff --git a/app/views/admins/projects_rank/index.html.erb b/app/views/admins/projects_rank/index.html.erb new file mode 100644 index 000000000..056e0a0da --- /dev/null +++ b/app/views/admins/projects_rank/index.html.erb @@ -0,0 +1,74 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('项目排行榜', admins_path) %> +<% end %> + + +
+ <%= form_tag(admins_projects_rank_index_path, method: :get, class: 'form-inline search-form flex-1', id: 'project-rank-date-form') do %> +
+ + <% dates_array = (0..30).to_a.map { |item| [(Date.today-item.days).to_s, (Date.today-item.days).to_s] } %> + <%= select_tag(:date, options_for_select(dates_array, params[:date]), class:"form-control",id: "project-rank-date-select")%> +
+ <% end %> + +
+ +
+ + + + + + + + + + + + + + + + + <% @date_rank.each_with_index do |item, index| %> + + + <% project_common = $redis_cache.hgetall("v2-project-common:#{item[0]}") %> + <% owner_common = $redis_cache.hgetall("v2-owner-common:#{project_common["owner_id"]}")%> + + + + <% project_date_statistic_key = "v2-project-statistic:#{item[0]}-#{@rank_date}"%> + <% if $redis_cache.exists(project_date_statistic_key)%> + <% visits = $redis_cache.hget(project_date_statistic_key, "visits") %> + + <% watchers = $redis_cache.hget(project_date_statistic_key, "watchers") %> + + <% praises = $redis_cache.hget(project_date_statistic_key, "praises") %> + + <% forks = $redis_cache.hget(project_date_statistic_key, "forks") %> + + <% issues = $redis_cache.hget(project_date_statistic_key, "issues") %> + + <% pullrequests = $redis_cache.hget(project_date_statistic_key, "pullrequests") %> + + <% commits = $redis_cache.hget(project_date_statistic_key, "commits") %> + + <% else %> + + <% end %> + + <% end %> + +
排名项目得分访问数关注数点赞数fork数疑修数合并请求数提交数
<%= index + 1%> + /<%= project_common["identifier"]%>"> + <%= project_common["name"] %> + + <%= item[1] %><%= visits || 0 %><%= watchers || 0 %><%= praises || 0 %><%= forks || 0 %><%= issues || 0 %><%= pullrequests || 0 %><%= commits || 0 %>暂无数据
+
+ \ No newline at end of file diff --git a/app/views/admins/shared/_sidebar.html.erb b/app/views/admins/shared/_sidebar.html.erb index afd0dad68..fb96f088e 100644 --- a/app/views/admins/shared/_sidebar.html.erb +++ b/app/views/admins/shared/_sidebar.html.erb @@ -14,6 +14,12 @@