| @@ -99,3 +99,38 @@ $(document).on("turbolinks:before-cache", function () { | |||
| $(function () { | |||
| }); | |||
| $(document).on('turbolinks:load', function() { | |||
| $('.logo-item-left').on("change", 'input[type="file"]', function () { | |||
| var $fileInput = $(this); | |||
| var file = this.files[0]; | |||
| var imageType = /image.*/; | |||
| if (file && file.type.match(imageType)) { | |||
| var reader = new FileReader(); | |||
| reader.onload = function () { | |||
| var $box = $fileInput.parent(); | |||
| $box.find('img').attr('src', reader.result).css('display', 'block'); | |||
| $box.addClass('has-img'); | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } else { | |||
| } | |||
| }); | |||
| $('.attachment-item-left').on("change", 'input[type="file"]', function () { | |||
| var $fileInput = $(this); | |||
| var file = this.files[0]; | |||
| var imageType = /image.*/; | |||
| if (file && file.type.match(imageType)) { | |||
| var reader = new FileReader(); | |||
| reader.onload = function () { | |||
| var $box = $fileInput.parent(); | |||
| $box.find('img').attr('src', reader.result).css('display', 'block'); | |||
| $box.addClass('has-img'); | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } else { | |||
| } | |||
| }); | |||
| }) | |||
| @@ -0,0 +1,141 @@ | |||
| /* | |||
| * @Description: Do not edit | |||
| * @Date: 2021-08-31 11:16:45 | |||
| * @LastEditors: viletyy | |||
| * @Author: viletyy | |||
| * @LastEditTime: 2021-08-31 14:19:46 | |||
| * @FilePath: /forgeplus/app/assets/javascripts/admins/system_notifications/index.js | |||
| */ | |||
| $(document).on('turbolinks:load', function(){ | |||
| var showSuccessNotify = function() { | |||
| $.notify({ | |||
| message: '操作成功' | |||
| },{ | |||
| type: 'success' | |||
| }); | |||
| } | |||
| // close user | |||
| $('.project-list-container').on('click', '.recommend-action', function(){ | |||
| var $closeAction = $(this); | |||
| var $uncloseAction = $closeAction.siblings('.unrecommend-action'); | |||
| var $editAction = $closeAction.siblings('.edit-recommend-action'); | |||
| var keywordID = $closeAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认将该项目设置为推荐项目吗?', | |||
| ok: function(){ | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| recommend: true, | |||
| recommend_index: 1 | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.hide(); | |||
| $uncloseAction.show(); | |||
| $editAction.show(); | |||
| $(".project-item-"+keywordID).children('td').eq(5).text("√") | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| }); | |||
| // unclose user | |||
| $('.project-list-container').on('click', '.unrecommend-action', function(){ | |||
| var $uncloseAction = $(this); | |||
| var $closeAction = $uncloseAction.siblings('.recommend-action'); | |||
| var $editAction = $closeAction.siblings('.edit-recommend-action'); | |||
| var keywordID = $uncloseAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认取消该推荐项目吗?', | |||
| ok: function () { | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| recommend: false, | |||
| recommend_index: 0 | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.show(); | |||
| $uncloseAction.hide(); | |||
| $editAction.hide(); | |||
| $(".project-item-"+keywordID).children('td').eq(5).text("") | |||
| } | |||
| }); | |||
| } | |||
| }) | |||
| }); | |||
| // close user | |||
| $('.project-list-container').on('click', '.pinned-action', function(){ | |||
| var $closeAction = $(this); | |||
| var $uncloseAction = $closeAction.siblings('.unpinned-action'); | |||
| var keywordID = $closeAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认将该项目设置为精选项目吗?', | |||
| ok: function(){ | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| is_pinned: true, | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.hide(); | |||
| $uncloseAction.show(); | |||
| $(".project-item-"+keywordID).children('td').eq(4).text("√") | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| }); | |||
| // unclose user | |||
| $('.project-list-container').on('click', '.unpinned-action', function(){ | |||
| var $uncloseAction = $(this); | |||
| var $closeAction = $uncloseAction.siblings('.pinned-action'); | |||
| var keywordID = $uncloseAction.data('id'); | |||
| customConfirm({ | |||
| content: '确认取消该精选项目吗?', | |||
| ok: function () { | |||
| $.ajax({ | |||
| url: '/admins/projects/' + keywordID, | |||
| method: 'PUT', | |||
| dataType: 'json', | |||
| data: { | |||
| project: { | |||
| is_pinned: false, | |||
| } | |||
| }, | |||
| success: function() { | |||
| showSuccessNotify(); | |||
| $closeAction.show(); | |||
| $uncloseAction.hide(); | |||
| $(".project-item-"+keywordID).children('td').eq(4).text("") | |||
| } | |||
| }); | |||
| } | |||
| }) | |||
| }); | |||
| }) | |||
| @@ -58,3 +58,149 @@ input.form-control { | |||
| position: absolute; | |||
| } | |||
| .logo-item { | |||
| display: flex; | |||
| &-img { | |||
| display: block; | |||
| width: 80px; | |||
| height: 80px; | |||
| background: #e9ecef; | |||
| } | |||
| &-upload { | |||
| cursor: pointer; | |||
| position: absolute; | |||
| top: 0; | |||
| width: 80px; | |||
| height: 80px; | |||
| background: #e9ecef; | |||
| border: 1px solid #ced4da; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 27px; | |||
| left: 39px; | |||
| width: 2px; | |||
| height: 26px; | |||
| background: #495057; | |||
| } | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 39px; | |||
| left: 27px; | |||
| width: 26px; | |||
| height: 2px; | |||
| background: #495057; | |||
| } | |||
| } | |||
| &-left { | |||
| position: relative; | |||
| width: 80px; | |||
| height: 80px; | |||
| &.has-img { | |||
| .logo-item-upload { | |||
| display: none; | |||
| } | |||
| &:hover { | |||
| .logo-item-upload { | |||
| display: block; | |||
| background: rgba(145, 145, 145, 0.8); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &-right { | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| color: #777777; | |||
| font-size: 0.8rem; | |||
| } | |||
| &-title { | |||
| color: #23272B; | |||
| font-size: 1rem; | |||
| } | |||
| } | |||
| .attachment-item { | |||
| display: flex; | |||
| &-img { | |||
| display: block; | |||
| width: 160px; | |||
| height: 160px; | |||
| background: #e9ecef; | |||
| } | |||
| &-upload { | |||
| cursor: pointer; | |||
| position: absolute; | |||
| top: 0; | |||
| width: 160px; | |||
| height: 160px; | |||
| background: #e9ecef; | |||
| border: 1px solid #ced4da; | |||
| &::before { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 54px; | |||
| left: 78px; | |||
| width: 2px; | |||
| height: 52px; | |||
| background: #495057; | |||
| } | |||
| &::after { | |||
| content: ''; | |||
| position: absolute; | |||
| top: 78px; | |||
| left: 54px; | |||
| width: 52px; | |||
| height: 2px; | |||
| background: #495057; | |||
| } | |||
| } | |||
| &-left { | |||
| position: relative; | |||
| width: 160px; | |||
| height: 160px; | |||
| &.has-img { | |||
| .attachment-item-upload { | |||
| display: none; | |||
| } | |||
| &:hover { | |||
| .attachment-item-upload { | |||
| display: block; | |||
| background: rgba(145, 145, 145, 0.8); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &-right { | |||
| padding-top: 100px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| color: #777777; | |||
| font-size: 0.8rem; | |||
| } | |||
| &-title { | |||
| color: #23272B; | |||
| font-size: 1rem; | |||
| } | |||
| } | |||
| @@ -22,7 +22,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| max_position_items = ProjectCategory.select(:id, :position).pluck(:position).reject!(&:blank?) | |||
| max_position = max_position_items.present? ? max_position_items.max.to_i : 0 | |||
| @project_category = ProjectCategory.new(name: @name,position: max_position) | |||
| @project_category = ProjectCategory.new(name: @name,position: max_position, pinned_index: params[:project_category][:pinned_index].to_i) | |||
| if @project_category.save | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = '创建成功' | |||
| @@ -33,7 +33,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| end | |||
| def update | |||
| if @project_category.update_attribute(:name, @name) | |||
| if @project_category.update_attributes({name: @name, pinned_index: params[:project_category][:pinned_index].to_i}) && save_image_file(params[:logo], 'logo') | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = '更新成功' | |||
| else | |||
| @@ -43,7 +43,7 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| end | |||
| def destroy | |||
| if @project_language.destroy | |||
| if @project_category.destroy | |||
| redirect_to admins_project_categories_path | |||
| flash[:success] = "删除成功" | |||
| else | |||
| @@ -80,4 +80,12 @@ class Admins::ProjectCategoriesController < Admins::BaseController | |||
| flash[:danger] = '分类已存在' | |||
| end | |||
| end | |||
| def save_image_file(file, type) | |||
| return unless file.present? && file.is_a?(ActionDispatch::Http::UploadedFile) | |||
| file_path = Util::FileManage.source_disk_filename(@project_category, type) | |||
| File.delete(file_path) if File.exist?(file_path) # 删除之前的文件 | |||
| Util.write_file(file, file_path) | |||
| end | |||
| end | |||
| @@ -1,4 +1,5 @@ | |||
| class Admins::ProjectsController < Admins::BaseController | |||
| before_action :find_project, only: [:edit, :update] | |||
| def index | |||
| sort_by = Project.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_on' | |||
| @@ -8,6 +9,26 @@ class Admins::ProjectsController < Admins::BaseController | |||
| @projects = paginate projects.includes(:owner, :members, :issues, :versions, :attachments, :project_score) | |||
| end | |||
| def edit ;end | |||
| def update | |||
| respond_to do |format| | |||
| if @project.update_attributes(project_update_params) | |||
| format.html do | |||
| redirect_to admins_projects_path | |||
| flash[:sucess] = "更新成功" | |||
| end | |||
| format.js {render_ok} | |||
| else | |||
| format.html do | |||
| redirect_to admins_projects_path | |||
| flash[:danger] = "更新失败" | |||
| end | |||
| format.js {render_js_error} | |||
| end | |||
| end | |||
| end | |||
| def destroy | |||
| project = Project.find_by!(id: params[:id]) | |||
| ActiveRecord::Base.transaction do | |||
| @@ -21,4 +42,13 @@ class Admins::ProjectsController < Admins::BaseController | |||
| redirect_to admins_projects_path | |||
| flash[:danger] = "删除失败" | |||
| end | |||
| private | |||
| def find_project | |||
| @project = Project.find_by_id(params[:id]) | |||
| end | |||
| def project_update_params | |||
| params.require(:project).permit(:is_pinned, :recommend, :recommend_index) | |||
| end | |||
| end | |||
| @@ -854,4 +854,8 @@ class ApplicationController < ActionController::Base | |||
| HotSearchKeyword.add(keyword) | |||
| end | |||
| def find_atme_receivers | |||
| @atme_receivers = User.where(login: params[:receivers_login]) | |||
| end | |||
| end | |||
| @@ -9,7 +9,7 @@ class IssuesController < ApplicationController | |||
| before_action :check_project_public, only: [:index ,:show, :copy, :index_chosen, :close_issue] | |||
| before_action :set_issue, only: [:edit, :update, :destroy, :show, :copy, :close_issue, :lock_issue] | |||
| before_action :check_token_enough, only: [:create, :update] | |||
| before_action :check_token_enough, :find_atme_receivers, only: [:create, :update] | |||
| include ApplicationHelper | |||
| include TagChosenHelper | |||
| @@ -142,6 +142,10 @@ class IssuesController < ApplicationController | |||
| end | |||
| @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") | |||
| 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 | |||
| render json: {status: 0, message: "创建成", id: @issue.id} | |||
| else | |||
| normal_status(-1, "创建失败") | |||
| @@ -244,6 +248,10 @@ class IssuesController < ApplicationController | |||
| post_to_chain(change_type, change_token.abs, current_user.try(:login)) | |||
| end | |||
| @issue.create_journal_detail(change_files, issue_files, issue_file_ids, current_user&.id) if @issue.previous_changes.present? | |||
| 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 | |||
| normal_status(0, "更新成功") | |||
| else | |||
| normal_status(-1, "更新失败") | |||
| @@ -1,6 +1,6 @@ | |||
| class JournalsController < ApplicationController | |||
| before_action :require_login, except: [:index, :get_children_journals] | |||
| before_action :require_profile_completed, only: [:create] | |||
| before_action :require_profile_completed, :find_atme_receivers, only: [:create] | |||
| before_action :set_issue | |||
| before_action :check_issue_permission | |||
| before_action :set_journal, only: [:destroy, :edit, :update] | |||
| @@ -22,32 +22,35 @@ class JournalsController < ApplicationController | |||
| if notes.blank? | |||
| normal_status(-1, "评论内容不能为空") | |||
| else | |||
| journal_params = { | |||
| journalized_id: @issue.id , | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: notes.to_s.strip, | |||
| parent_id: params[:parent_id] | |||
| } | |||
| journal = Journal.new journal_params | |||
| if journal.save | |||
| if params[:attachment_ids].present? | |||
| params[:attachment_ids].each do |id| | |||
| attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) | |||
| unless attachment.blank? | |||
| attachment.container = journal | |||
| attachment.author_id = current_user.id | |||
| attachment.description = "" | |||
| attachment.save | |||
| ActiveRecord::Base.transaction do | |||
| journal_params = { | |||
| journalized_id: @issue.id , | |||
| journalized_type: "Issue", | |||
| user_id: current_user.id , | |||
| notes: notes.to_s.strip, | |||
| parent_id: params[:parent_id] | |||
| } | |||
| journal = Journal.new journal_params | |||
| if journal.save | |||
| if params[:attachment_ids].present? | |||
| params[:attachment_ids].each do |id| | |||
| attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) | |||
| unless attachment.blank? | |||
| attachment.container = journal | |||
| attachment.author_id = current_user.id | |||
| attachment.description = "" | |||
| attachment.save | |||
| end | |||
| end | |||
| end | |||
| Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" | |||
| AtmeService.call(current_user, @atme_receivers, journal) if @atme_receivers.size > 0 | |||
| # @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal") | |||
| render :json => { status: 0, message: "评论成功", id: journal.id} | |||
| # normal_status(0, "评论成功") | |||
| else | |||
| normal_status(-1, "评论失败") | |||
| end | |||
| # @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal") | |||
| render :json => { status: 0, message: "评论成功", id: journal.id} | |||
| # normal_status(0, "评论成功") | |||
| else | |||
| normal_status(-1, "评论失败") | |||
| end | |||
| end | |||
| end | |||
| @@ -22,6 +22,7 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| @can_create_project = @organization.can_create_project?(current_user.id) | |||
| @is_admin = can_edit_org? | |||
| @is_member = @organization.is_member?(current_user.id) | |||
| Cache::V2::OwnerCommonService.new(@organization.id).read | |||
| end | |||
| def create | |||
| @@ -68,8 +69,7 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| def recommend | |||
| recommend = %W(xuos Huawei_Technology openatom_foundation pkecosystem TensorLayer) | |||
| @organizations = Organization.with_visibility(%w(common)) | |||
| .where(login: recommend).select(:id, :login, :firstname, :lastname, :nickname) | |||
| @organizations = Organization.includes(:organization_extension).where(organization_extensions: {recommend: true}).to_a.each_slice(group_size).to_a | |||
| end | |||
| private | |||
| @@ -80,6 +80,10 @@ class Organizations::OrganizationsController < Organizations::BaseController | |||
| :max_repo_creation, :nickname) | |||
| end | |||
| def group_size | |||
| params.fetch(:group_size, 4).to_i | |||
| end | |||
| def password | |||
| params.fetch(:password, "") | |||
| end | |||
| @@ -5,6 +5,10 @@ class ProjectCategoriesController < ApplicationController | |||
| @project_categories = q.result(distinct: true) | |||
| end | |||
| def pinned_index | |||
| @project_categories = ProjectCategory.where.not(pinned_index: 0).order(pinned_index: :desc) | |||
| end | |||
| def group_list | |||
| @project_categories = ProjectCategory.where('projects_count > 0').order(projects_count: :desc) | |||
| # projects = Project.no_anomory_projects.visible | |||
| @@ -0,0 +1,26 @@ | |||
| class ProjectRankController < ApplicationController | |||
| # 根据时间获取热门项目 | |||
| def index | |||
| $redis_cache.zunionstore("recent-days-project-rank", get_timeable_key_names) | |||
| deleted_data = $redis_cache.smembers("v2-project-rank-deleted") | |||
| $redis_cache.zrem("recent-days-project-rank", deleted_data) unless deleted_data.blank? | |||
| @project_rank = $redis_cache.zrevrange("recent-days-project-rank", 0, 4, withscores: true) | |||
| rescue Exception => e | |||
| @project_rank = [] | |||
| end | |||
| private | |||
| # 默认显示7天的 | |||
| def time | |||
| params.fetch(:time, 7).to_i | |||
| end | |||
| def get_timeable_key_names | |||
| names_array = [] | |||
| (0...time).to_a.each do |i| | |||
| date_time_string = (Date.today - i.days).to_s | |||
| names_array << "v2-project-rank-#{date_time_string}" | |||
| end | |||
| names_array | |||
| end | |||
| end | |||
| @@ -0,0 +1,6 @@ | |||
| class Projects::MembersController < Projects::BaseController | |||
| def index | |||
| users = @project.all_collaborators.like(params[:search]).includes(:user_extension) | |||
| @users = kaminari_paginate(users) | |||
| end | |||
| end | |||
| @@ -4,9 +4,9 @@ class ProjectsController < ApplicationController | |||
| include ProjectsHelper | |||
| include Acceleratorable | |||
| before_action :require_login, except: %i[index branches branches_slice group_type_list simple show fork_users praise_users watch_users recommend about menu_list] | |||
| before_action :require_login, except: %i[index branches branches_slice group_type_list simple show fork_users praise_users watch_users recommend banner_recommend about menu_list] | |||
| before_action :require_profile_completed, only: [:create, :migrate] | |||
| before_action :load_repository, except: %i[index group_type_list migrate create recommend] | |||
| before_action :load_repository, except: %i[index group_type_list migrate create recommend banner_recommend] | |||
| before_action :authorizate_user_can_edit_project!, only: %i[update] | |||
| before_action :project_public?, only: %i[fork_users praise_users watch_users] | |||
| @@ -30,8 +30,8 @@ class ProjectsController < ApplicationController | |||
| def index | |||
| scope = current_user.logged? ? Projects::ListQuery.call(params, current_user.id) : Projects::ListQuery.call(params) | |||
| # @projects = kaminari_paginate(scope) | |||
| @projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units) | |||
| @projects = kaminari_paginate(scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units)) | |||
| # @projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units) | |||
| category_id = params[:category_id] | |||
| @total_count = | |||
| @@ -190,6 +190,8 @@ class ProjectsController < ApplicationController | |||
| end | |||
| def simple | |||
| # 为了缓存活跃项目的基本信息,后续删除 | |||
| Cache::V2::ProjectCommonService.new(@project.id).read | |||
| json_response(@project, current_user) | |||
| end | |||
| @@ -197,6 +199,10 @@ class ProjectsController < ApplicationController | |||
| @projects = Project.recommend.includes(:repository, :project_category, :owner).order(visits: :desc) | |||
| end | |||
| def banner_recommend | |||
| @projects = Project.recommend.where.not(recommend_index: 0).includes(:project_category, :owner, :project_language).order(recommend_index: :desc) | |||
| end | |||
| def about | |||
| @project_detail = @project.project_detail | |||
| @attachments = Array(@project_detail&.attachments) if request.get? | |||
| @@ -5,6 +5,7 @@ class PullRequestsController < ApplicationController | |||
| before_action :check_menu_authorize | |||
| before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos, :files, :commits] | |||
| before_action :load_pull_request, only: [:files, :commits] | |||
| before_action :find_atme_receivers, only: [:create, :update] | |||
| include TagChosenHelper | |||
| include ApplicationHelper | |||
| @@ -61,6 +62,8 @@ class PullRequestsController < ApplicationController | |||
| @pull_request.bind_gitea_pull_request!(@gitea_pull_request[:body]["number"], @gitea_pull_request[:body]["id"]) | |||
| SendTemplateMessageJob.perform_later('PullRequestAssigned', current_user.id, @pull_request&.id) if Site.has_notice_menu? | |||
| SendTemplateMessageJob.perform_later('ProjectPullRequest', current_user.id, @pull_request&.id) if Site.has_notice_menu? | |||
| Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" | |||
| AtmeService.call(current_user, @atme_receivers, @pull_request) if @atme_receivers.size > 0 | |||
| else | |||
| render_error("create pull request error: #{@gitea_pull_request[:status]}") | |||
| raise ActiveRecord::Rollback | |||
| @@ -106,6 +109,8 @@ class PullRequestsController < ApplicationController | |||
| if params[:status_id].to_i == 5 | |||
| @issue.issue_times.update_all(end_time: Time.now) | |||
| end | |||
| Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" | |||
| AtmeService.call(current_user, @atme_receivers, @pull_request) if @atme_receivers.size > 0 | |||
| normal_status(0, "PullRequest更新成功") | |||
| else | |||
| normal_status(-1, "PullRequest更新失败") | |||
| @@ -48,7 +48,7 @@ class RepositoriesController < ApplicationController | |||
| def entries | |||
| @project.increment!(:visits) | |||
| CacheAsyncSetJob.perform_later("project_common_service", {visits: 1}, @project.id) | |||
| if @project.educoder? | |||
| @entries = Educoder::Repository::Entries::ListService.call(@project&.project_educoder.repo_name) | |||
| else | |||
| @@ -0,0 +1,24 @@ | |||
| class UserRankController < ApplicationController | |||
| # 根据时间获取热门开发者 | |||
| def index | |||
| $redis_cache.zunionstore("recent-days-user-rank", get_timeable_key_names) | |||
| @user_rank = $redis_cache.zrevrange("recent-days-user-rank", 0, 3, withscores: true) | |||
| rescue Exception => e | |||
| @user_rank = [] | |||
| end | |||
| private | |||
| # 默认显示7天的 | |||
| def time | |||
| params.fetch(:time, 7).to_i | |||
| end | |||
| def get_timeable_key_names | |||
| names_array = [] | |||
| (0...time).to_a.each do |i| | |||
| date_time_string = (Date.today - i.days).to_s | |||
| names_array << "v2-user-rank-#{date_time_string}" | |||
| end | |||
| names_array | |||
| end | |||
| end | |||
| @@ -188,30 +188,32 @@ class Users::StatisticsController < Users::BaseController | |||
| @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 | |||
| @platform_result = Cache::V2::PlatformStatisticService.new.read | |||
| @user_result = Cache::V2::UserStatisticService.new(observed_user.id).read | |||
| # 用户被follow数量 | |||
| @follow_count = Cache::UserFollowCountService.call(observed_user) | |||
| @platform_follow_count = Cache::PlatformFollowCountService.call | |||
| @follow_count = @user_result["follow-count"].to_i | |||
| @platform_follow_count = @platform_result["follow-count"].to_i | |||
| # 用户pr数量 | |||
| @pullrequest_count = Cache::UserPullrequestCountService.call(observed_user) | |||
| @platform_pullrequest_count = Cache::PlatformPullrequestCountService.call | |||
| @pullrequest_count = @user_result["pullrequest-count"].to_i | |||
| @platform_pullrequest_count = @platform_result["pullrequest-count"].to_i | |||
| # 用户issue数量 | |||
| @issues_count = Cache::UserIssueCountService.call(observed_user) | |||
| @platform_issues_count = Cache::PlatformIssueCountService.call | |||
| @issues_count = @user_result["issue-count"].to_i | |||
| @platform_issues_count = @platform_result["issue-count"].to_i | |||
| # 用户总项目数 | |||
| @project_count = Cache::UserProjectCountService.call(observed_user) | |||
| @platform_project_count = Cache::PlatformProjectCountService.call | |||
| @project_count = @user_result["project-count"].to_i | |||
| @platform_project_count = @platform_result["project-count"].to_i | |||
| # 用户项目被fork数量 | |||
| @fork_count = Cache::UserProjectForkCountService.call(observed_user) | |||
| @platform_fork_count = Cache::PlatformProjectForkCountService.call | |||
| @fork_count = @user_result["fork-count"].to_i | |||
| @platform_fork_count = @platform_result["fork-count"].to_i | |||
| # 用户项目关注数 | |||
| @project_watchers_count = Cache::UserProjectWatchersCountService.call(observed_user) | |||
| @platform_project_watchers_count = Cache::PlatformProjectWatchersCountService.call | |||
| @project_watchers_count = @user_result["project-watcher-count"].to_i | |||
| @platform_project_watchers_count = @platform_result["project-watcher-count"].to_i | |||
| # 用户项目点赞数 | |||
| @project_praises_count = Cache::UserProjectPraisesCountService.call(observed_user) | |||
| @platform_project_praises_count = Cache::PlatformProjectPraisesCountService.call | |||
| @project_praises_count = @user_result["project-praise-count"].to_i | |||
| @platform_project_praises_count = @platform_result["project-praise-count"].to_i | |||
| # 用户不同语言项目数量 | |||
| @project_languages_count = Cache::UserProjectLanguagesCountService.call(observed_user) | |||
| @platform_project_languages_count = Cache::PlatformProjectLanguagesCountService.call | |||
| @project_languages_count = JSON.parse(@user_result["project-language"]) | |||
| @platform_project_languages_count = JSON.parse(@platform_result["project-language"]) | |||
| end | |||
| end | |||
| end | |||
| @@ -51,6 +51,8 @@ class UsersController < ApplicationController | |||
| @projects_common_count = user_projects.common.size | |||
| @projects_mirrior_count = user_projects.mirror.size | |||
| @projects_sync_mirrior_count = user_projects.sync_mirror.size | |||
| # 为了缓存活跃用户的基本信息,后续删除 | |||
| Cache::V2::OwnerCommonService.new(@user.id).read | |||
| end | |||
| def watch_users | |||
| @@ -191,7 +193,7 @@ class UsersController < ApplicationController | |||
| def trustie_related_projects | |||
| projects = Project.includes(:owner, :members, :project_score).where(id: params[:ids]).order("updated_on desc") | |||
| projects_json = [] | |||
| domain_url = EduSetting.get('host_name') + '/projects' | |||
| domain_url = EduSetting.get('host_name') | |||
| if projects.present? | |||
| projects.each do |p| | |||
| project_url = "/#{p.owner.login}/#{p.identifier}" | |||
| @@ -3,11 +3,12 @@ class Projects::UpdateForm < BaseForm | |||
| validates :name, presence: true | |||
| validates :name, length: { maximum: 50 } | |||
| validates :description, length: { maximum: 200 } | |||
| validates :identifier, format: { with: CustomRegexp::REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } | |||
| validate do | |||
| check_project_category(project_category_id) | |||
| check_project_language(project_language_id) | |||
| Rails.logger.info project_identifier | |||
| Rails.logger.info identifier | |||
| check_repository_name(user_id, identifier) unless identifier.blank? || identifier == project_identifier | |||
| end | |||
| @@ -442,6 +442,14 @@ module ApplicationHelper | |||
| User.find_by(gitea_uid: gitea_uid) | |||
| end | |||
| def find_user_in_redis_cache(login, email) | |||
| $redis_cache.hgetall("v2-owner-common:#{login}-#{email}") | |||
| end | |||
| def find_user_in_redis_cache_by_id(id) | |||
| $redis_cache.hgetall("v2-owner-common:#{id}") | |||
| end | |||
| def render_base64_decoded(str) | |||
| return nil if str.blank? | |||
| Base64.decode64 str | |||
| @@ -0,0 +1,26 @@ | |||
| module AvatarHelper | |||
| def relative_path | |||
| "avatars" | |||
| end | |||
| def storage_path | |||
| File.join(Rails.root, "public", "images", relative_path) | |||
| end | |||
| def disk_filename(source_type,source_id,image_file=nil) | |||
| File.join(storage_path, "#{source_type}", "#{source_id}") | |||
| end | |||
| def url_to_avatar(source) | |||
| if File.exist?(disk_filename(source&.class, source&.id)) | |||
| ctime = File.ctime(disk_filename(source.class, source.id)).to_i | |||
| if %w(User Organization).include?(source.class.to_s) | |||
| File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" | |||
| else | |||
| File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}" | |||
| end | |||
| elsif source.class.to_s == 'User' | |||
| source.get_letter_avatar_url | |||
| end | |||
| end | |||
| end | |||
| @@ -35,6 +35,16 @@ module RepositoriesHelper | |||
| end | |||
| end | |||
| def render_cache_commit_author(author_json) | |||
| Rails.logger.info author_json['Email'] | |||
| if author_json["name"].present? && author_json["email"].present? | |||
| return find_user_in_redis_cache(author_json['name'], author_json['email']) | |||
| end | |||
| if author_json["Name"].present? && author_json["Email"].present? | |||
| return find_user_in_redis_cache(author_json['Name'], author_json['Email']) | |||
| end | |||
| end | |||
| def readme_render_decode64_content(str, path) | |||
| return nil if str.blank? | |||
| begin | |||
| @@ -0,0 +1,12 @@ | |||
| class CacheAsyncClearJob < ApplicationJob | |||
| queue_as :cache | |||
| def perform(type, id=nil) | |||
| case type | |||
| when "project_common_service" | |||
| Cache::V2::ProjectCommonService.new(id).clear | |||
| when "owner_common_service" | |||
| Cache::V2::OwnnerCommonService.new(id).clear | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,16 @@ | |||
| class CacheAsyncResetJob < ApplicationJob | |||
| queue_as :cache | |||
| def perform(type, id=nil) | |||
| case type | |||
| when "platform_statistic_service" | |||
| Cache::V2::PlatformStatisticService.new.reset | |||
| when "project_common_service" | |||
| Cache::V2::ProjectCommonService.new(id).reset | |||
| when "owner_common_service" | |||
| Cache::V2::OwnnerCommonService.new(id).reset | |||
| when "user_statistic_service" | |||
| Cache::V2::UserStatisticService.new(id).reset | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,16 @@ | |||
| class CacheAsyncSetJob < ApplicationJob | |||
| queue_as :cache | |||
| def perform(type, params={}, id=nil) | |||
| case type | |||
| when "platform_statistic_service" | |||
| Cache::V2::PlatformStatisticService.new(params).call | |||
| when "project_common_service" | |||
| Cache::V2::ProjectCommonService.new(id, params).call | |||
| when "owner_common_service" | |||
| Cache::V2::OwnnerCommonService.new(id, params).call | |||
| when "user_statistic_service" | |||
| Cache::V2::UserStatisticService.new(id, params).call | |||
| end | |||
| end | |||
| end | |||
| @@ -17,14 +17,6 @@ class ApplicationRecord < ActiveRecord::Base | |||
| 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 | |||
| def self.strip_param(key) | |||
| key.to_s.strip.presence | |||
| end | |||
| @@ -20,12 +20,30 @@ 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 | |||
| after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic | |||
| after_destroy :decre_project_common, :decre_user_statistic, :decre_platform_statistic | |||
| def reset_cache_data | |||
| self.reset_platform_cache_async_job | |||
| self.reset_user_cache_async_job(self.project.owner) | |||
| def incre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {forks: 1}, self.project_id) | |||
| end | |||
| def decre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {forks: -1}, self.project_id) | |||
| end | |||
| def incre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {fork_count: 1}, self.project&.user_id) | |||
| end | |||
| def decre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {fork_count: -1}, self.project&.user_id) | |||
| end | |||
| def incre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {fork_count: 1}) | |||
| end | |||
| def decre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {fork_count: -1}) | |||
| end | |||
| end | |||
| @@ -74,13 +74,32 @@ class Issue < ApplicationRecord | |||
| 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_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic | |||
| after_update :change_versions_count | |||
| after_save :reset_cache_data | |||
| after_destroy :update_closed_issues_count_in_project!, :reset_cache_data | |||
| after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic | |||
| def reset_cache_data | |||
| self.reset_platform_cache_async_job | |||
| self.reset_user_cache_async_job(self.user) | |||
| def incre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {issues: 1}, self.project_id) | |||
| end | |||
| def decre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {issues: -1}, self.project_id) | |||
| end | |||
| def incre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {issue_count: 1}, self.author_id) | |||
| end | |||
| def decre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {issue_count: -1}, self.author_id) | |||
| end | |||
| def incre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {issue_count: 1}) | |||
| end | |||
| def decre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {issue_count: -1}) | |||
| end | |||
| def get_assign_user | |||
| @@ -76,11 +76,17 @@ class Organization < Owner | |||
| validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, case_sensitive: false | |||
| validates :login, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } | |||
| delegate :description, :website, :location, :repo_admin_change_team_access, | |||
| delegate :description, :website, :location, :repo_admin_change_team_access, :recommend, | |||
| :visibility, :max_repo_creation, :num_projects, :num_users, :num_teams, to: :organization_extension, allow_nil: true | |||
| scope :with_visibility, ->(visibility) { joins(:organization_extension).where(organization_extensions: {visibility: visibility}) if visibility.present? } | |||
| after_save :reset_cache_data | |||
| def reset_cache_data | |||
| Cache::V2::OwnerCommonService.new(self.id).reset | |||
| end | |||
| def self.build(name, nickname, gitea_token=nil) | |||
| self.create!(login: name, nickname: nickname, gitea_token: gitea_token) | |||
| end | |||
| @@ -15,6 +15,7 @@ | |||
| # num_projects :integer default("0") | |||
| # num_users :integer default("0") | |||
| # num_teams :integer default("0") | |||
| # recommend :boolean default("0") | |||
| # | |||
| # Indexes | |||
| # | |||
| @@ -30,6 +31,8 @@ class OrganizationExtension < ApplicationRecord | |||
| enum visibility: {common: 0, limited: 1, privacy: 2} | |||
| before_save :set_recommend | |||
| def self.build(organization_id, description, website, location, repo_admin_change_team_access, visibility, max_repo_creation) | |||
| self.create!(organization_id: organization_id, | |||
| description: description, | |||
| @@ -39,4 +42,9 @@ class OrganizationExtension < ApplicationRecord | |||
| visibility: visibility, | |||
| max_repo_creation: max_repo_creation) | |||
| end | |||
| private | |||
| def set_recommend | |||
| self.recommend = false unless self.common? | |||
| end | |||
| end | |||
| @@ -1,35 +1,51 @@ | |||
| # == Schema Information | |||
| # | |||
| # Table name: praise_treads | |||
| # | |||
| # id :integer not null, primary key | |||
| # user_id :integer not null | |||
| # praise_tread_object_id :integer | |||
| # praise_tread_object_type :string(255) | |||
| # praise_or_tread :integer default("1") | |||
| # created_at :datetime not null | |||
| # updated_at :datetime not null | |||
| # | |||
| # Indexes | |||
| # | |||
| # praise_tread (praise_tread_object_id,praise_tread_object_type) | |||
| # | |||
| # == Schema Information | |||
| # | |||
| # Table name: praise_treads | |||
| # | |||
| # id :integer not null, primary key | |||
| # user_id :integer not null | |||
| # praise_tread_object_id :integer | |||
| # praise_tread_object_type :string(255) | |||
| # praise_or_tread :integer default("1") | |||
| # created_at :datetime not null | |||
| # updated_at :datetime not null | |||
| # | |||
| # Indexes | |||
| # | |||
| # 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 | |||
| after_create :send_tiding, :incre_project_common, :incre_user_statistic, :incre_platform_statistic | |||
| after_destroy :decre_project_common, :decre_user_statistic, :decre_platform_statistic | |||
| 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 | |||
| def incre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {praises: 1}, self.praise_tread_object_id) if self.praise_tread_object_type == "Project" | |||
| end | |||
| def decre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {praises: -1}, self.praise_tread_object_id) if self.praise_tread_object_type == "Project" | |||
| end | |||
| def incre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_praise_count: 1}, self.praise_tread_object&.user_id) if self.praise_tread_object_type == "Project" | |||
| end | |||
| def decre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_praise_count: -1}, self.praise_tread_object&.user_id) if self.praise_tread_object_type == "Project" | |||
| end | |||
| def incre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_praise_count: 1}) if self.praise_tread_object_type == "Project" | |||
| end | |||
| def decre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_praise_count: -1}) if self.praise_tread_object_type == "Project" | |||
| end | |||
| def send_tiding | |||
| @@ -55,6 +55,8 @@ | |||
| # default_branch :string(255) default("master") | |||
| # website :string(255) | |||
| # lesson_url :string(255) | |||
| # is_pinned :boolean default("0") | |||
| # recommend_index :integer default("0") | |||
| # | |||
| # Indexes | |||
| # | |||
| @@ -76,6 +78,8 @@ | |||
| # | |||
| class Project < ApplicationRecord | |||
| include Matchable | |||
| include Publicable | |||
| @@ -123,13 +127,15 @@ class Project < ApplicationRecord | |||
| has_many :pinned_projects, dependent: :destroy | |||
| has_many :has_pinned_users, through: :pinned_projects, source: :user | |||
| has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id | |||
| after_save :check_project_members | |||
| before_save :set_invite_code, :reset_cache_data, :reset_unmember_followed | |||
| 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)} | |||
| after_create :incre_user_statistic, :incre_platform_statistic | |||
| after_save :check_project_members, :reset_cache_data | |||
| before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned | |||
| before_destroy :decre_project_common | |||
| after_destroy :decre_user_statistic, :decre_platform_statistic | |||
| scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :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) } | |||
| scope :pinned, -> {where(is_pinned: true)} | |||
| delegate :content, to: :project_detail, allow_nil: true | |||
| delegate :name, to: :license, prefix: true, allow_nil: true | |||
| @@ -147,12 +153,48 @@ class Project < ApplicationRecord | |||
| end | |||
| def reset_cache_data | |||
| CacheAsyncResetJob.perform_later("project_common_service", self.id) | |||
| if changes[:user_id].present? | |||
| first_owner = Owner.find_by_id(changes[:user_id].first) | |||
| self.reset_user_cache_async_job(first_owner) | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first) | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last) | |||
| end | |||
| if changes[:project_language_id].present? | |||
| first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first) | |||
| last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last) | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id) | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id) | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}) | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}) | |||
| end | |||
| end | |||
| def decre_project_common | |||
| CacheAsyncClearJob.perform_later('project_common_service', self.id) | |||
| end | |||
| def incre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}, self.user_id) | |||
| end | |||
| def decre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}, self.user_id) | |||
| end | |||
| def incre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}) | |||
| end | |||
| def decre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}) | |||
| end | |||
| def is_full_public | |||
| owner = self.owner | |||
| if owner.is_a?(Organization) | |||
| return self.is_public && owner&.visibility == "common" | |||
| else | |||
| return self.is_public | |||
| end | |||
| self.reset_platform_cache_async_job | |||
| self.reset_user_cache_async_job(self.owner) | |||
| end | |||
| def reset_unmember_followed | |||
| @@ -167,6 +209,16 @@ class Project < ApplicationRecord | |||
| end | |||
| end | |||
| def set_recommend_and_is_pinned | |||
| self.recommend = self.recommend_index.zero? ? false : true | |||
| # 私有项目不允许设置精选和推荐 | |||
| unless self.is_public | |||
| self.recommend = false | |||
| self.recommend_index = 0 | |||
| self.is_pinned = false | |||
| end | |||
| end | |||
| def self.search_project(search) | |||
| ransack(name_or_identifier_cont: search) | |||
| end | |||
| @@ -8,10 +8,27 @@ | |||
| # projects_count :integer default("0") | |||
| # created_at :datetime not null | |||
| # updated_at :datetime not null | |||
| # ancestry :string(255) | |||
| # pinned_index :integer default("0") | |||
| # | |||
| # Indexes | |||
| # | |||
| # index_project_categories_on_ancestry (ancestry) | |||
| # | |||
| class ProjectCategory < ApplicationRecord | |||
| include Projectable | |||
| has_ancestry | |||
| def logo_url | |||
| image_url('logo') | |||
| end | |||
| private | |||
| def image_url(type) | |||
| return nil unless Util::FileManage.exists?(self, type) | |||
| Util::FileManage.source_disk_file_url(self, type) | |||
| end | |||
| end | |||
| @@ -42,12 +42,31 @@ class PullRequest < ApplicationRecord | |||
| scope :merged_and_closed, ->{where.not(status: 0)} | |||
| scope :opening, -> {where(status: 0)} | |||
| after_save :reset_cache_data | |||
| after_destroy :reset_cache_data | |||
| after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic | |||
| after_destroy :decre_project_common, :decre_user_statistic, :decre_platform_statistic | |||
| def reset_cache_data | |||
| self.reset_platform_cache_async_job | |||
| self.reset_user_cache_async_job(self.user) | |||
| def incre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {pullrequests: 1}, self.project_id) | |||
| end | |||
| def decre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {pullrequests: -1}, self.project_id) | |||
| end | |||
| def incre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {pullrequest_count: 1}, self.user_id) | |||
| end | |||
| def decre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {pullrequest_count: -1}, self.user_id) | |||
| end | |||
| def incre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {pullrequest_count: 1}) | |||
| end | |||
| def decre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {pullrequest_count: -1}) | |||
| end | |||
| def fork_project | |||
| @@ -190,6 +190,7 @@ class User < Owner | |||
| :technical_title, :province, :city, :custom_department, to: :user_extension, allow_nil: true | |||
| before_save :update_hashed_password, :set_lastname | |||
| after_save :reset_cache_data | |||
| after_create do | |||
| SyncTrustieJob.perform_later("user", 1) if allow_sync_to_trustie? | |||
| end | |||
| @@ -206,6 +207,10 @@ class User < Owner | |||
| validate :validate_sensitive_string | |||
| validate :validate_password_length | |||
| def reset_cache_data | |||
| Cache::V2::OwnerCommonService.new(self.id).reset | |||
| end | |||
| # 用户参与的所有项目 | |||
| def full_member_projects | |||
| normal_projects = Project.members_projects(self.id).to_sql | |||
| @@ -22,18 +22,36 @@ class Watcher < ApplicationRecord | |||
| scope :watching_users, ->(watchable_id){ where("watchable_type = ? and user_id = ?",'User',watchable_id)} | |||
| after_save :reset_cache_data | |||
| after_destroy :reset_cache_data | |||
| after_create :send_create_message_to_notice_system | |||
| 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 | |||
| after_create :send_create_message_to_notice_system, :incre_project_common, :incre_user_statistic, :incre_platform_statistic | |||
| after_destroy :decre_project_common, :decre_user_statistic, :decre_platform_statistic | |||
| def incre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {watchers: 1}, self.watchable_id) if self.watchable_type == "Project" | |||
| end | |||
| def decre_project_common | |||
| CacheAsyncSetJob.perform_later("project_common_service", {watchers: -1}, self.watchable_id) if self.watchable_type == "Project" | |||
| end | |||
| def incre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {follow_count: 1}, self.watchable_id) if self.watchable_type == "User" | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_watcher_count: 1}, self.watchable&.user_id) if self.watchable_type == "Project" | |||
| end | |||
| def decre_user_statistic | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {follow_count: -1}, self.watchable_id) if self.watchable_type == "User" | |||
| CacheAsyncSetJob.perform_later("user_statistic_service", {project_watcher_count: -1}, self.watchable&.user_id) if self.watchable_type == "Project" | |||
| end | |||
| def incre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {follow_count: 1}) if self.watchable_type == "User" | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_watcher_count: 1}) if self.watchable_type == "Project" | |||
| end | |||
| def decre_platform_statistic | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {follow_count: -1}) if self.watchable_type == "User" | |||
| CacheAsyncSetJob.perform_later("platform_statistic_service", {project_watcher_count: -1}) if self.watchable_type == "Project" | |||
| end | |||
| def send_create_message_to_notice_system | |||
| @@ -11,7 +11,8 @@ class Projects::ListQuery < ApplicationQuery | |||
| end | |||
| def call | |||
| q = Project.visible.by_name_or_identifier(params[:search]) | |||
| q = params[:pinned].present? ? Project.pinned : Project | |||
| q = q.visible.by_name_or_identifier(params[:search]) | |||
| scope = q | |||
| .with_project_type(params[:project_type]) | |||
| @@ -0,0 +1,37 @@ | |||
| class AtmeService < ApplicationService | |||
| Error = Class.new(StandardError) | |||
| attr_reader :user, :receivers, :atmeable | |||
| def initialize(user, receivers, atmeable) | |||
| @user = user | |||
| @receivers = receivers | |||
| @atmeable = atmeable | |||
| end | |||
| def call | |||
| Rails.logger.info "[ATME] service args: [user]=>#{user}, [receivers]=>#{receivers}, [atmeable]=>#{atmeable}" | |||
| return if atmeable.nil? | |||
| Rails.logger.info "[ATME] atmeable class name is: #{ atmeable.class.name}" | |||
| case atmeable.class.name | |||
| when 'Issue' | |||
| message_source = 'IssueAtme' | |||
| when 'PullRequest' | |||
| message_source = 'PullRequestAtme' | |||
| when 'Journal' | |||
| journal = Journal.find_by_id(atmeable.id) | |||
| if journal.present? | |||
| if journal&.issue&.pull_request.present? | |||
| @atmeable = journal&.issue&.pull_request | |||
| message_source = 'PullRequestAtme' | |||
| else | |||
| @atmeable = journal&.issue | |||
| message_source = 'IssueAtme' | |||
| end | |||
| end | |||
| else | |||
| return | |||
| end | |||
| SendTemplateMessageJob.perform_now(message_source, receivers, user.id, @atmeable.id) if Site.has_notice_menu? | |||
| end | |||
| end | |||
| @@ -0,0 +1,135 @@ | |||
| class Cache::V2::OwnerCommonService < ApplicationService | |||
| include AvatarHelper | |||
| attr_reader :owner_id, :name | |||
| attr_accessor :owner, :login, :email | |||
| def initialize(owner_id, params={}) | |||
| @owner_id = owner_id | |||
| @email = params[:email] | |||
| @name = params[:name] | |||
| @avatar_url = params[:avatar_url] | |||
| end | |||
| def read | |||
| owner_common | |||
| end | |||
| def call | |||
| set_owner_common | |||
| end | |||
| def reset | |||
| reset_owner_common | |||
| end | |||
| def clear | |||
| clear_owner_common | |||
| end | |||
| private | |||
| def load_owner | |||
| @owner = Owner.find_by_id @owner_id | |||
| @login = @owner&.login | |||
| @email ||= @owner&.mail | |||
| end | |||
| def owner_common_key | |||
| "v2-owner-common:#{@login}-#{@email.to_s}" | |||
| end | |||
| def owner_common_key_by_id | |||
| "v2-owner-common:#{@owner&.id}" | |||
| end | |||
| def owner_common | |||
| result = $redis_cache.hgetall(owner_common_key_by_id) | |||
| result.blank? ? reset_owner_common : result | |||
| end | |||
| def set_owner_common | |||
| if $redis_cache.hgetall(owner_common_key_by_id).blank? | |||
| reset_owner_common | |||
| return | |||
| else | |||
| load_owner | |||
| return if @owner.nil? | |||
| if @name.present? | |||
| if $redis_cache.hget(owner_common_key, "name").nil? | |||
| reset_owner_name | |||
| else | |||
| $redis_cache.hset(owner_common_key, "name", @name) | |||
| $redis_cache.hset(owner_common_key_by_id, "name", @name) | |||
| end | |||
| end | |||
| if @email.present? | |||
| if $redis_cache.hget(owner_common_key, "email").nil? | |||
| reset_owner_email | |||
| else | |||
| # 更改邮箱这里把旧数据删除 | |||
| $redis_cache.del("v2-owner-common:#{@login}-*") | |||
| $redis_cache.hset(owner_common_key, "email", @email) | |||
| $redis_cache.hset(owner_common_key_by_id, "email", @email) | |||
| end | |||
| end | |||
| if @avatar_url.present? | |||
| if $redis_cache.hget(owner_common_key, "avatar_url").nil? | |||
| reset_owner_avatar_url | |||
| else | |||
| $redis_cache.hset(owner_common_key, "avatar_url", @avatar_url) | |||
| $redis_cache.hset(owner_common_key_by_id, "avatar_url", @avatar_url) | |||
| end | |||
| end | |||
| end | |||
| $redis_cache.hgetall(owner_common_key) | |||
| end | |||
| def reset_owner_id | |||
| $redis_cache.hset(owner_common_key, "id", owner&.id) | |||
| $redis_cache.hset(owner_common_key_by_id, "id", owner&.id) | |||
| end | |||
| def reset_owner_type | |||
| $redis_cache.hset(owner_common_key, "type", owner&.type) | |||
| $redis_cache.hset(owner_common_key_by_id, "type", owner&.type) | |||
| end | |||
| def reset_owner_login | |||
| $redis_cache.hset(owner_common_key, "login", owner&.login) | |||
| $redis_cache.hset(owner_common_key_by_id, "login", owner&.login) | |||
| end | |||
| def reset_owner_email | |||
| $redis_cache.hset(owner_common_key, "email", owner&.mail) | |||
| $redis_cache.hset(owner_common_key_by_id, "email", owner&.mail) | |||
| end | |||
| def reset_owner_name | |||
| $redis_cache.hset(owner_common_key, "name", owner&.real_name) | |||
| $redis_cache.hset(owner_common_key_by_id, "name", owner&.real_name) | |||
| end | |||
| def reset_owner_avatar_url | |||
| $redis_cache.hset(owner_common_key, "avatar_url", url_to_avatar(owner)) | |||
| $redis_cache.hset(owner_common_key_by_id, "avatar_url", url_to_avatar(owner)) | |||
| end | |||
| def reset_owner_common | |||
| clear_owner_common | |||
| reset_owner_id | |||
| reset_owner_type | |||
| reset_owner_login | |||
| reset_owner_email | |||
| reset_owner_name | |||
| reset_owner_avatar_url | |||
| $redis_cache.hgetall(owner_common_key) | |||
| end | |||
| def clear_owner_common | |||
| load_owner | |||
| return if @owner.nil? | |||
| $redis_cache.del(owner_common_key) | |||
| $redis_cache.del(owner_common_key_by_id) | |||
| end | |||
| end | |||
| @@ -0,0 +1,184 @@ | |||
| class Cache::V2::PlatformStatisticService < ApplicationService | |||
| attr_reader :follow_count, :fork_count, :issue_count, :project_count, :project_language_count_key, :project_language_count, :project_praise_count, :project_watcher_count, :pullrequest_count | |||
| def initialize(params={}) | |||
| @follow_count = params[:follow_count] | |||
| @fork_count = params[:fork_count] | |||
| @issue_count = params[:issue_count] | |||
| @project_count = params[:project_count] | |||
| @project_language_count_key = params[:project_language_count_key] | |||
| @project_language_count = params[:project_language_count] | |||
| @project_praise_count = params[:project_praise_count] | |||
| @project_watcher_count = params[:project_watcher_count] | |||
| @pullrequest_count = params[:pullrequest_count] | |||
| end | |||
| def read | |||
| platform_statistic | |||
| end | |||
| def call | |||
| set_platform_statistic | |||
| end | |||
| def reset | |||
| reset_platform_statistic | |||
| end | |||
| private | |||
| def platform_statistic_key | |||
| "v2-platform-statistic" | |||
| end | |||
| def follow_count_key | |||
| "follow-count" | |||
| end | |||
| def fork_count_key | |||
| "fork-count" | |||
| end | |||
| def issue_count_key | |||
| "issue-count" | |||
| end | |||
| def project_count_key | |||
| "project-count" | |||
| end | |||
| def project_language_key | |||
| "project-language" | |||
| end | |||
| def project_praise_count_key | |||
| "project-praise-count" | |||
| end | |||
| def project_watcher_count_key | |||
| "project-watcher-count" | |||
| end | |||
| def pullrequest_count_key | |||
| "pullrequest-count" | |||
| end | |||
| def platform_statistic | |||
| result = $redis_cache.hgetall(platform_statistic_key) | |||
| result.blank? ? reset_platform_statistic : result | |||
| end | |||
| def set_platform_statistic | |||
| if $redis_cache.hgetall(platform_statistic_key).blank? | |||
| reset_platform_statistic | |||
| return | |||
| end | |||
| if @follow_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, follow_count_key).nil? | |||
| reset_platform_follow_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, follow_count_key, @follow_count) | |||
| end | |||
| end | |||
| if @fork_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, fork_count_key).nil? | |||
| reset_platform_fork_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, fork_count_key, @fork_count) | |||
| end | |||
| end | |||
| if @issue_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, issue_count_key).nil? | |||
| reset_platform_issue_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, issue_count_key, @issue_count) | |||
| end | |||
| end | |||
| if @project_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, project_count_key).nil? | |||
| reset_platform_project_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, project_count_key, @project_count) | |||
| end | |||
| end | |||
| if @project_language_count_key.present? && project_language_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, project_language_key).nil? | |||
| reset_platform_project_language | |||
| else | |||
| result = JSON.parse($redis_cache.hget(platform_statistic_key, project_language_key)) | |||
| result[@project_language_count_key] ||= 0 | |||
| result[@project_language_count_key] += project_language_count.to_i | |||
| $redis_cache.hset(platform_statistic_key, project_language_key, result.to_json) | |||
| end | |||
| end | |||
| if @project_praise_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, project_praise_count_key).nil? | |||
| reset_platform_project_praise_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, project_praise_count_key, @project_praise_count) | |||
| end | |||
| end | |||
| if @project_watcher_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, project_watcher_count_key).nil? | |||
| reset_platform_project_watcher_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, project_watcher_count_key, @project_watcher_count) | |||
| end | |||
| end | |||
| if @pullrequest_count.present? | |||
| if $redis_cache.hget(platform_statistic_key, pullrequest_count_key).nil? | |||
| reset_platform_pullrequest_count | |||
| else | |||
| $redis_cache.hincrby(platform_statistic_key, pullrequest_count_key, @pullrequest_count) | |||
| end | |||
| end | |||
| $redis_cache.hgetall(platform_statistic_key) | |||
| end | |||
| def reset_platform_follow_count | |||
| $redis_cache.hset(platform_statistic_key, follow_count_key, Watcher.where(watchable_type: 'User').count) | |||
| end | |||
| def reset_platform_fork_count | |||
| $redis_cache.hset(platform_statistic_key, fork_count_key, ForkUser.count) | |||
| end | |||
| def reset_platform_issue_count | |||
| $redis_cache.hset(platform_statistic_key, issue_count_key, Issue.count) | |||
| end | |||
| def reset_platform_project_count | |||
| $redis_cache.hset(platform_statistic_key, project_count_key, Project.count) | |||
| end | |||
| def reset_platform_project_language | |||
| $redis_cache.hset(platform_statistic_key, project_language_key, ProjectLanguage.where.not(projects_count: 0).group("project_languages.name").sum(:projects_count).to_json) | |||
| end | |||
| def reset_platform_project_praise_count | |||
| $redis_cache.hset(platform_statistic_key, project_praise_count_key, PraiseTread.where(praise_tread_object_type: "Project").count) | |||
| end | |||
| def reset_platform_project_watcher_count | |||
| $redis_cache.hset(platform_statistic_key, project_watcher_count_key, Watcher.where(watchable_type: 'Project').count) | |||
| end | |||
| def reset_platform_pullrequest_count | |||
| $redis_cache.hset(platform_statistic_key, pullrequest_count_key, PullRequest.count) | |||
| end | |||
| def reset_platform_statistic | |||
| $redis_cache.del(platform_statistic_key) | |||
| reset_platform_follow_count | |||
| reset_platform_fork_count | |||
| reset_platform_issue_count | |||
| reset_platform_project_count | |||
| reset_platform_project_language | |||
| reset_platform_project_praise_count | |||
| reset_platform_project_watcher_count | |||
| reset_platform_pullrequest_count | |||
| $redis_cache.hgetall(platform_statistic_key) | |||
| end | |||
| end | |||
| @@ -0,0 +1,219 @@ | |||
| class Cache::V2::ProjectCommonService < ApplicationService | |||
| attr_reader :project_id, :owner_id, :name, :identifier, :description, :visits, :watchers, :praises, :forks, :issues, :pullrequests | |||
| attr_accessor :project | |||
| def initialize(project_id, params={}) | |||
| @project_id = project_id | |||
| @owner_id = params[:owner_id] | |||
| @name = params[:name] | |||
| @identifier = params[:identifier] | |||
| @description = params[:description] | |||
| @visits = params[:visits] | |||
| @watchers = params[:watchers] | |||
| @praises = params[:praises] | |||
| @forks = params[:forks] | |||
| @issues = params[:issues] | |||
| @pullrequests = params[:pullrequests] | |||
| end | |||
| def read | |||
| project_common | |||
| end | |||
| def call | |||
| set_project_common | |||
| end | |||
| def reset | |||
| reset_project_common | |||
| end | |||
| def clear | |||
| clear_project_common | |||
| end | |||
| private | |||
| def load_project | |||
| @project = Project.find_by_id(project_id) | |||
| end | |||
| def project_common_key | |||
| "v2-project-common:#{@project_id}" | |||
| end | |||
| def owner_id_key | |||
| "owner_id" | |||
| end | |||
| def name_key | |||
| "name" | |||
| end | |||
| def identifier_key | |||
| "identifier" | |||
| end | |||
| def description_key | |||
| "description" | |||
| end | |||
| def visits_key | |||
| "visits" | |||
| end | |||
| def watchers_key | |||
| "watchers" | |||
| end | |||
| def praises_key | |||
| "praises" | |||
| end | |||
| def forks_key | |||
| "forks" | |||
| end | |||
| def issues_key | |||
| "issues" | |||
| end | |||
| def pullrequests_key | |||
| "pullrequests" | |||
| end | |||
| def project_common | |||
| result = $redis_cache.hgetall(project_common_key) | |||
| result.blank? ? reset_project_common : result | |||
| end | |||
| def set_project_common | |||
| if $redis_cache.hgetall(project_common_key).blank? | |||
| reset_project_common | |||
| return | |||
| else | |||
| load_project | |||
| return unless @project.is_full_public | |||
| if @owner_id.present? | |||
| if $redis_cache.hget(project_common_key, owner_id_key).nil? | |||
| reset_project_owner_id | |||
| else | |||
| $redis_cache.hset(project_common_key, owner_id_key, @owner_id) | |||
| end | |||
| end | |||
| if @name.present? | |||
| if $redis_cache.hget(project_common_key, name_key).nil? | |||
| reset_project_name | |||
| else | |||
| $redis_cache.hset(project_common_key, name_key, @name) | |||
| end | |||
| end | |||
| if @identifier.present? | |||
| if $redis_cache.hget(project_common_key, identifier_key).nil? | |||
| reset_project_identifier | |||
| else | |||
| $redis_cache.hset(project_common_key, identifier_key, @identifier) | |||
| end | |||
| end | |||
| if @description.present? | |||
| if $redis_cache.hget(project_common_key, description_key).nil? | |||
| reset_project_description | |||
| else | |||
| $redis_cache.hset(project_common_key, description_key, @description) | |||
| end | |||
| end | |||
| if @visits.present? | |||
| $redis_cache.hincrby(project_common_key, visits_key, @visits.to_s) | |||
| Cache::V2::ProjectRankService.call(@project_id, {visits: @visits}) | |||
| Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {visits: @visits}) | |||
| end | |||
| if @watchers.present? | |||
| $redis_cache.hincrby(project_common_key, watchers_key, @watchers) | |||
| end | |||
| if @praises.present? | |||
| $redis_cache.hincrby(project_common_key, praises_key, @praises) | |||
| Cache::V2::ProjectRankService.call(@project_id, {praises: @praises}) | |||
| Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {praises: @praises}) | |||
| end | |||
| if @forks.present? | |||
| $redis_cache.hincrby(project_common_key, forks_key, @forks) | |||
| Cache::V2::ProjectRankService.call(@project_id, {forks: @forks}) | |||
| Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {forks: @forks}) | |||
| end | |||
| if @issues.present? | |||
| $redis_cache.hincrby(project_common_key, issues_key, @issues) | |||
| Cache::V2::ProjectRankService.call(@project_id, {issues: @issues}) | |||
| Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {issues: @issues}) | |||
| end | |||
| if @pullrequests.present? | |||
| $redis_cache.hincrby(project_common_key, pullrequests_key, @pullrequests) | |||
| Cache::V2::ProjectRankService.call(@project_id, {pullrequests: @pullrequests}) | |||
| Cache::V2::ProjectDateRankService.call(@project_id, Date.today, {pullrequests: @pullrequests}) | |||
| end | |||
| end | |||
| $redis_cache.hgetall(project_common_key) | |||
| end | |||
| def reset_project_owner_id | |||
| $redis_cache.hset(project_common_key, owner_id_key, @project&.user_id) | |||
| end | |||
| def reset_project_name | |||
| $redis_cache.hset(project_common_key, name_key, @project&.name) | |||
| end | |||
| def reset_project_identifier | |||
| $redis_cache.hset(project_common_key, identifier_key, @project&.identifier) | |||
| end | |||
| def reset_project_description | |||
| $redis_cache.hset(project_common_key, description_key, @project&.description) | |||
| end | |||
| def reset_project_visits | |||
| $redis_cache.hset(project_common_key, visits_key, @project&.visits || 0) | |||
| end | |||
| def reset_project_watchers | |||
| $redis_cache.hset(project_common_key, watchers_key, Watcher.where(watchable_type: 'Project', watchable_id: @project_id).count) | |||
| end | |||
| def reset_project_praises | |||
| $redis_cache.hset(project_common_key, praises_key, PraiseTread.where(praise_tread_object_type: 'Project', praise_tread_object_id: @project_id).count) | |||
| end | |||
| def reset_project_forks | |||
| $redis_cache.hset(project_common_key, forks_key, ForkUser.where(project_id: @project_id).count) | |||
| end | |||
| def reset_project_issues | |||
| $redis_cache.hset(project_common_key, issues_key, Issue.issue_issue.where(project_id: @project_id).count) | |||
| end | |||
| def reset_project_pullrequests | |||
| $redis_cache.hset(project_common_key, pullrequests_key, PullRequest.where(project_id: @project_id).count) | |||
| end | |||
| def reset_project_common | |||
| load_project | |||
| return unless @project.is_full_public | |||
| $redis_cache.del(project_common_key) | |||
| reset_project_owner_id | |||
| reset_project_name | |||
| reset_project_identifier | |||
| reset_project_description | |||
| reset_project_visits | |||
| reset_project_watchers | |||
| reset_project_praises | |||
| reset_project_forks | |||
| reset_project_issues | |||
| reset_project_pullrequests | |||
| $redis_cache.hgetall(project_common_key) | |||
| end | |||
| def clear_project_common | |||
| $redis_cache.del(project_common_key) | |||
| Cache::V2::ProjectRankService.new(@project_id).clear | |||
| end | |||
| end | |||
| @@ -0,0 +1,51 @@ | |||
| class Cache::V2::ProjectDateRankService < ApplicationService | |||
| attr_reader :project_id, :rank_date, :visits, :praises, :forks, :issues, :pullrequests | |||
| attr_accessor :project_common | |||
| def initialize(project_id, rank_date=Date.today, params={}) | |||
| @project_id = project_id | |||
| @rank_date = rank_date | |||
| @visits = params[:visits] | |||
| @praises = params[:praises] | |||
| @forks = params[:forks] | |||
| @issues = params[:issues] | |||
| @pullrequests = params[:pullrequests] | |||
| end | |||
| def read | |||
| project_rank | |||
| end | |||
| def call | |||
| set_project_rank | |||
| end | |||
| private | |||
| def project_rank_key | |||
| "v2-project-rank-#{@rank_date.to_s}" | |||
| end | |||
| def project_rank | |||
| $redis_cache.zscore(project_rank_key, @project_id) | |||
| end | |||
| def set_project_rank | |||
| if @visits.present? | |||
| $redis_cache.zincrby(project_rank_key, @visits.to_i * 1, @project_id) | |||
| end | |||
| if @praises.present? | |||
| $redis_cache.zincrby(project_rank_key, @praises.to_i * 5, @project_id) | |||
| end | |||
| if @forks.present? | |||
| $redis_cache.zincrby(project_rank_key, @forks.to_i * 5, @project_id) | |||
| end | |||
| if @issues.present? | |||
| $redis_cache.zincrby(project_rank_key, @issues.to_i * 10, @project_id) | |||
| end | |||
| if @pullrequests.present? | |||
| $redis_cache.zincrby(project_rank_key, @pullrequests.to_i * 10, @project_id) | |||
| end | |||
| $redis_cache.zscore(project_rank_key, @project_id) | |||
| end | |||
| end | |||
| @@ -0,0 +1,87 @@ | |||
| class Cache::V2::ProjectRankService < ApplicationService | |||
| attr_reader :project_id, :visits, :praises, :forks, :issues, :pullrequests | |||
| attr_accessor :project_common | |||
| def initialize(project_id, params={}) | |||
| @project_id = project_id | |||
| @visits = params[:visits] | |||
| @praises = params[:praises] | |||
| @forks = params[:forks] | |||
| @issues = params[:issues] | |||
| @pullrequests = params[:pullrequests] | |||
| end | |||
| def read | |||
| project_rank | |||
| end | |||
| def call | |||
| set_project_rank | |||
| end | |||
| def reset | |||
| reset_project_rank | |||
| end | |||
| def clear | |||
| clear_project_rank | |||
| end | |||
| private | |||
| def load_project_common | |||
| @project_common = Cache::V2::ProjectCommonService.new(@project_id).read | |||
| end | |||
| def project_rank_key | |||
| "v2-project-rank" | |||
| end | |||
| def project_rank | |||
| result = $redis_cache.zscore(project_rank_key, @project_id) | |||
| result.blank? ? reset_project_rank : result | |||
| end | |||
| def set_project_rank | |||
| load_project_common | |||
| if $redis_cache.zscore(project_rank_key, @project_id).blank? | |||
| reset_project_rank | |||
| return | |||
| else | |||
| if @visits.present? | |||
| $redis_cache.zincrby(project_rank_key, @visits.to_i * 1, @project_id) | |||
| end | |||
| if @praises.present? | |||
| $redis_cache.zincrby(project_rank_key, @praises.to_i * 5, @project_id) | |||
| end | |||
| if @forks.present? | |||
| $redis_cache.zincrby(project_rank_key, @forks.to_i * 5, @project_id) | |||
| end | |||
| if @issues.present? | |||
| $redis_cache.zincrby(project_rank_key, @issues.to_i * 10, @project_id) | |||
| end | |||
| if @pullrequests.present? | |||
| $redis_cache.zincrby(project_rank_key, @pullrequests.to_i * 10, @project_id) | |||
| end | |||
| reset_user_project_rank | |||
| end | |||
| $redis_cache.zscore(project_rank_key, @project_id) | |||
| end | |||
| def reset_project_rank | |||
| load_project_common | |||
| score = @project_common["visits"].to_i * 1 + @project_common["praises"].to_i * 5 + @project_common["forks"].to_i * 5 + @project_common["issues"].to_i * 10 + @project_common["pullrequests"].to_i * 10 | |||
| $redis_cache.zadd(project_rank_key, score, @project_id) | |||
| reset_user_project_rank | |||
| $redis_cache.zscore(project_rank_key, @project_id) | |||
| end | |||
| def reset_user_project_rank | |||
| $redis_cache.zadd("v2-user-project-rank:#{@project_common["owner_id"]}", $redis_cache.zscore(project_rank_key, @project_id), @project_id) | |||
| end | |||
| def clear_project_rank | |||
| $redis_cache.sadd('v2-project-rank-deleted', @project_id) | |||
| end | |||
| end | |||
| @@ -0,0 +1,119 @@ | |||
| class Cache::V2::UserDateRankService < ApplicationService | |||
| attr_reader :user_id, :rank_date, :follow_count, :fork_count, :issue_count, :project_count, :project_language_count_key, :project_language_count, :project_praise_count, :project_watcher_count, :pullrequest_count | |||
| def initialize(user_id, rank_date=Date.today, params={}) | |||
| @user_id = user_id | |||
| @rank_date = rank_date | |||
| @follow_count = params[:follow_count] | |||
| @fork_count = params[:fork_count] | |||
| @issue_count = params[:issue_count] | |||
| @project_count = params[:project_count] | |||
| @project_language_count_key = params[:project_language_count_key] | |||
| @project_language_count = params[:project_language_count] | |||
| @project_praise_count = params[:project_praise_count] | |||
| @project_watcher_count = params[:project_watcher_count] | |||
| @pullrequest_count = params[:pullrequest_count] | |||
| end | |||
| def read_rank | |||
| user_rank | |||
| end | |||
| def read_statistic | |||
| user_statistic | |||
| end | |||
| def call | |||
| set_user_rank | |||
| end | |||
| private | |||
| def user_rank_key | |||
| "v2-user-rank-#{@rank_date.to_s}" | |||
| end | |||
| def user_date_statistic_key | |||
| "v2-user-statistic:#{@user_id}-#{@rank_date.to_s}" | |||
| end | |||
| def user_rank | |||
| $redis_cache.zscore(user_rank_key, @user_id) | |||
| end | |||
| def user_statistic | |||
| $redis_cache.hgetall(user_date_statistic_key) | |||
| end | |||
| def set_user_statistic | |||
| if @follow_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "follow-count", @follow_count.to_i) | |||
| end | |||
| if @fork_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "fork-count", @fork_count.to_i) | |||
| end | |||
| if @issue_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "issue-count", @issue_count.to_i) | |||
| end | |||
| if @project_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "project-count", @project_count.to_i) | |||
| end | |||
| if project_language_count_key.present? && project_language_count.present? | |||
| if $redis_cache.hget(user_date_statistic_key, "project-language").nil? | |||
| result = {} | |||
| result[@project_language_count_key] = project_language_count.to_i | |||
| result.delete(@project_language_count_key) if result[@project_language_count_key] == 0 | |||
| $redis_cache.hset(user_date_statistic_key, "project-language", result.to_json) | |||
| else | |||
| result = JSON.parse($redis_cache.hget(user_date_statistic_key, "project-language")) | |||
| result[@project_language_count_key] ||= 0 | |||
| result[@project_language_count_key] += project_language_count.to_i | |||
| result.delete(@project_language_count_key) if result[@project_language_count_key] == 0 | |||
| $redis_cache.hset(user_date_statistic_key, "project-language", result.to_json) | |||
| end | |||
| end | |||
| if @project_praise_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "project-praise-count", @project_praise_count.to_i) | |||
| end | |||
| if @project_watcher_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "project-watcher-count", @project_watcher_count.to_i) | |||
| end | |||
| if @pullrequest_count.present? | |||
| $redis_cache.hincrby(user_date_statistic_key, "pullrequest-count", @pullrequest_count.to_i) | |||
| end | |||
| $redis_cache.hgetall(user_date_statistic_key) | |||
| end | |||
| def set_user_rank | |||
| set_user_statistic | |||
| follow_count = $redis_cache.hget(user_date_statistic_key, "follow-count") || 0 | |||
| pullrequest_count = $redis_cache.hget(user_date_statistic_key, "pullrequest-count") || 0 | |||
| issues_count = $redis_cache.hget(user_date_statistic_key, "issue-count") || 0 | |||
| project_count = $redis_cache.hget(user_date_statistic_key, "project-count") || 0 | |||
| fork_count = $redis_cache.hget(user_date_statistic_key, "fork-count") || 0 | |||
| project_watchers_count = $redis_cache.hget(user_date_statistic_key, "project-watcher-count") || 0 | |||
| project_praises_count = $redis_cache.hget(user_date_statistic_key, "project-praise-count") || 0 | |||
| project_language = $redis_cache.hget(user_date_statistic_key, "project-language") | |||
| project_languages_count = project_language.nil? || project_language == "{}" ? 0 : JSON.parse(project_language).length | |||
| # 影响力 | |||
| influence = (60.0 + follow_count.to_i / (follow_count.to_i + 20.0) * 40.0).to_i | |||
| # 贡献度 | |||
| contribution = (60.0 + pullrequest_count.to_i / (pullrequest_count.to_i + 20.0) * 40.0).to_i | |||
| # 活跃度 | |||
| activity = (60.0 + issues_count.to_i / (issues_count.to_i + 80.0) * 40.0).to_i | |||
| # 项目经验 | |||
| experience = 10 * project_count.to_i + 5 * fork_count.to_i + project_watchers_count.to_i + project_praises_count.to_i | |||
| experience = (60.0 + experience / (experience + 100.0) * 40.0).to_i | |||
| # 语言能力 | |||
| language = (60.0 + project_languages_count.to_i / (project_languages_count.to_i + 5.0) * 40.0).to_i | |||
| score = influence+ contribution + activity + experience + language | |||
| $redis_cache.zrem(user_rank_key, @user_id) | |||
| $redis_cache.zadd(user_rank_key, score-300, @user_id) if score > 300 | |||
| $redis_cache.zscore(user_rank_key, @user_id) | |||
| end | |||
| end | |||
| @@ -0,0 +1,202 @@ | |||
| class Cache::V2::UserStatisticService < ApplicationService | |||
| attr_reader :user_id, :follow_count, :fork_count, :issue_count, :project_count, :project_language_count_key, :project_language_count, :project_praise_count, :project_watcher_count, :pullrequest_count | |||
| def initialize(user_id, params={}) | |||
| @user_id = user_id | |||
| @follow_count = params[:follow_count] | |||
| @fork_count = params[:fork_count] | |||
| @issue_count = params[:issue_count] | |||
| @project_count = params[:project_count] | |||
| @project_language_count_key = params[:project_language_count_key] | |||
| @project_language_count = params[:project_language_count] | |||
| @project_praise_count = params[:project_praise_count] | |||
| @project_watcher_count = params[:project_watcher_count] | |||
| @pullrequest_count = params[:pullrequest_count] | |||
| Cache::V2::OwnerCommonService.new(user_id).read | |||
| end | |||
| def read | |||
| user_statistic | |||
| end | |||
| def call | |||
| set_user_statistic | |||
| end | |||
| def reset | |||
| reset_user_statistic | |||
| end | |||
| private | |||
| def user_statistic_key | |||
| "v2-user-statistic:#{@user_id}" | |||
| end | |||
| def follow_count_key | |||
| "follow-count" | |||
| end | |||
| def fork_count_key | |||
| "fork-count" | |||
| end | |||
| def issue_count_key | |||
| "issue-count" | |||
| end | |||
| def project_count_key | |||
| "project-count" | |||
| end | |||
| def project_language_key | |||
| "project-language" | |||
| end | |||
| def project_praise_count_key | |||
| "project-praise-count" | |||
| end | |||
| def project_watcher_count_key | |||
| "project-watcher-count" | |||
| end | |||
| def pullrequest_count_key | |||
| "pullrequest-count" | |||
| end | |||
| def user_statistic | |||
| result = $redis_cache.hgetall(user_statistic_key) | |||
| result.blank? ? reset_user_statistic : result | |||
| end | |||
| def set_user_statistic | |||
| if $redis_cache.hgetall(user_statistic_key).blank? | |||
| reset_user_statistic | |||
| return | |||
| end | |||
| if @follow_count.present? | |||
| if $redis_cache.hget(user_statistic_key, follow_count_key).nil? | |||
| reset_user_follow_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {follow_count: @follow_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, follow_count_key, @follow_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {follow_count: @follow_count}) | |||
| end | |||
| end | |||
| if @fork_count.present? | |||
| if $redis_cache.hget(user_statistic_key, fork_count_key).nil? | |||
| reset_user_fork_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {fork_count: @fork_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, fork_count_key, @fork_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {fork_count: @fork_count}) | |||
| end | |||
| end | |||
| if @issue_count.present? | |||
| if $redis_cache.hget(user_statistic_key, issue_count_key).nil? | |||
| reset_user_issue_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {issue_count: @issue_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, issue_count_key, @issue_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {issue_count: @issue_count}) | |||
| end | |||
| end | |||
| if @project_count.present? | |||
| if $redis_cache.hget(user_statistic_key, project_count_key).nil? | |||
| reset_user_project_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_count: @project_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, project_count_key, @project_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_count: @project_count}) | |||
| end | |||
| end | |||
| if @project_language_count_key.present? && project_language_count.present? | |||
| if $redis_cache.hget(user_statistic_key, project_language_key).nil? | |||
| reset_user_project_language | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_language_count_key: @project_language_count_key, project_language_count: @project_language_count}) | |||
| else | |||
| result = JSON.parse($redis_cache.hget(user_statistic_key, project_language_key)) | |||
| result[@project_language_count_key] ||= 0 | |||
| result[@project_language_count_key] += project_language_count.to_i | |||
| result.delete(@project_language_count_key) if result[@project_language_count_key] == 0 | |||
| $redis_cache.hset(user_statistic_key, project_language_key, result.to_json) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_language_count_key: @project_language_count_key, project_language_count: @project_language_count}) | |||
| end | |||
| end | |||
| if @project_praise_count.present? | |||
| if $redis_cache.hget(user_statistic_key, project_praise_count_key).nil? | |||
| reset_user_project_praise_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_praise_count: @project_praise_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, project_praise_count_key, @project_praise_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_praise_count: @project_praise_count}) | |||
| end | |||
| end | |||
| if @project_watcher_count.present? | |||
| if $redis_cache.hget(user_statistic_key, project_watcher_count_key).nil? | |||
| reset_user_project_watcher_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_watcher_count: @project_watcher_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, project_watcher_count_key, @project_watcher_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {project_watcher_count: @project_watcher_count}) | |||
| end | |||
| end | |||
| if @pullrequest_count.present? | |||
| if $redis_cache.hget(user_statistic_key, pullrequest_count_key).nil? | |||
| reset_user_pullrequest_count | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {pullrequest_count: @pullrequest_count}) | |||
| else | |||
| $redis_cache.hincrby(user_statistic_key, pullrequest_count_key, @pullrequest_count) | |||
| Cache::V2::UserDateRankService.call(@user_id, Date.today, {pullrequest_count: @pullrequest_count}) | |||
| end | |||
| end | |||
| $redis_cache.hgetall(user_statistic_key) | |||
| end | |||
| def reset_user_follow_count | |||
| $redis_cache.hset(user_statistic_key, follow_count_key, Watcher.where(watchable_type: 'User', watchable_id: @user_id).count) | |||
| end | |||
| def reset_user_fork_count | |||
| $redis_cache.hset(user_statistic_key, fork_count_key, ForkUser.joins(:project).where(projects: {user_id: @user_id}).count) | |||
| end | |||
| def reset_user_issue_count | |||
| $redis_cache.hset(user_statistic_key, issue_count_key, Issue.where(author_id: @user_id).count) | |||
| end | |||
| def reset_user_project_count | |||
| $redis_cache.hset(user_statistic_key, project_count_key, Project.where(user_id: @user_id).count) | |||
| end | |||
| def reset_user_project_language | |||
| $redis_cache.hset(user_statistic_key, project_language_key, Project.where(user_id: @user_id).joins(:project_language).group("project_languages.name").count.to_json) | |||
| end | |||
| def reset_user_project_praise_count | |||
| $redis_cache.hset(user_statistic_key, project_praise_count_key, PraiseTread.where(praise_tread_object_type: 'Project', praise_tread_object_id: Project.where(user_id: @user_id)).count) | |||
| end | |||
| def reset_user_project_watcher_count | |||
| $redis_cache.hset(user_statistic_key, project_watcher_count_key, Watcher.where(watchable_type: 'Project', watchable_id: Project.where(user_id: @user_id)).count) | |||
| end | |||
| def reset_user_pullrequest_count | |||
| $redis_cache.hset(user_statistic_key, pullrequest_count_key, PullRequest.where(user_id: @user_id).count) | |||
| end | |||
| def reset_user_statistic | |||
| $redis_cache.del(user_statistic_key) | |||
| reset_user_follow_count | |||
| reset_user_fork_count | |||
| reset_user_issue_count | |||
| reset_user_project_count | |||
| reset_user_project_language | |||
| reset_user_project_praise_count | |||
| reset_user_project_watcher_count | |||
| reset_user_pullrequest_count | |||
| $redis_cache.hgetall(user_statistic_key) | |||
| end | |||
| end | |||
| @@ -7,9 +7,33 @@ | |||
| <span aria-hidden="true">×</span> | |||
| </button> | |||
| </div> | |||
| <%= form_for @project_category, url: {controller: "project_categories", action: "#{type}"} do |p| %> | |||
| <%= form_for @project_category, url: {controller: "project_categories", action: "#{type}"}, html: { enctype: 'multipart/form-data' } do |p| %> | |||
| <div class="modal-body"> | |||
| <%= p.text_field :name,class: "form-control input-lg",placeholder: "分类名称",required: true, maxlength: 64%> | |||
| <div class="form-group"> | |||
| <label> | |||
| 分类名称 <span class="ml10 color-orange mr20">*</span> | |||
| </label> | |||
| <%= p.text_field :name,class: "form-control input-lg",placeholder: "分类名称",required: true, maxlength: 64%> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label> | |||
| 精选等级 | |||
| </label> | |||
| <%= p.number_field :pinned_index,class: "form-control input-lg",placeholder: "精选等级",required: true%> | |||
| </div> | |||
| <div class="logo-item"> | |||
| <% logo_img = @project_category.logo_url %> | |||
| <div class="logo-item-left mr-3 <%= logo_img ? 'has-img' : '' %>"> | |||
| <img class="logo-item-img nav-logo-img" src="<%= logo_img %>" style="<%= logo_img.present? ? '' : 'display: none' %>"/> | |||
| <%= file_field_tag(:logo, accept: 'image/png,image/jpg,image/jpeg',style: "display: none", value: params[:logo]) %> | |||
| <label for="logo" class="logo-item-upload" data-toggle="tooltip" data-title="选择图片"></label> | |||
| </div> | |||
| <div class="logo-item-right"> | |||
| <div class="logo-item-title flex-1">logo</div> | |||
| <div>格式:PNG、JPG</div> | |||
| <div>尺寸:高度38px以内,宽等比例缩放</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="modal-footer"> | |||
| <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button> | |||
| @@ -3,7 +3,9 @@ | |||
| <tr> | |||
| <th width="5%">序号</th> | |||
| <th width="30%">名称</th> | |||
| <th width="20%"><%= sort_tag('精选', name: 'pinned_index', path: admins_project_categories_path) %></th> | |||
| <th width="20%"><%= sort_tag('项目数', name: 'projects_count', path: admins_project_categories_path) %></th> | |||
| <th width="20%">精选项目数</th> | |||
| <th width="20%"><%= sort_tag('创建时间', name: 'created_at', path: admins_project_categories_path) %></th> | |||
| <th width="25%">操作</th> | |||
| </tr> | |||
| @@ -16,7 +18,9 @@ | |||
| <td> | |||
| <%= link_to(project_category.name, "/projects?category_id=#{project_category.id}", target: '_blank') %> | |||
| </td> | |||
| <td><%= project_category.pinned_index == 0 ? "" : "√" %></td> | |||
| <td><%= project_category.projects_count %></td> | |||
| <td><%= project_category.projects.select(:id).where(is_pinned: true).size %></td> | |||
| <td><%= project_category.created_at&.strftime('%Y-%m-%d %H:%M') %></td> | |||
| <td class="action-container"> | |||
| <%= link_to "编辑", edit_admins_project_category_path(project_category), remote: true, class: "action" %> | |||
| @@ -1,2 +1,19 @@ | |||
| $("#project-category-modals").html("<%= j render(partial: 'admins/project_categories/form_modal', locals: {type: 'update'}) %>") | |||
| $(".project-category-change-modal").modal('show'); | |||
| $(".project-category-change-modal").modal('show'); | |||
| $('.logo-item-left').on("change", 'input[type="file"]', function () { | |||
| var $fileInput = $(this); | |||
| var file = this.files[0]; | |||
| var imageType = /image.*/; | |||
| if (file && file.type.match(imageType)) { | |||
| var reader = new FileReader(); | |||
| reader.onload = function () { | |||
| var $box = $fileInput.parent(); | |||
| $box.find('img').attr('src', reader.result).css('display', 'block'); | |||
| $box.addClass('has-img'); | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } else { | |||
| } | |||
| }); | |||
| @@ -0,0 +1,2 @@ | |||
| $("#projects-modals").html("<%= j render(partial: 'admins/projects/shared/form_modal', locals: {type: 'update'}) %>") | |||
| $(".project-change-modal").modal('show'); | |||
| @@ -12,4 +12,7 @@ | |||
| <div class="box admin-list-container project-list-container"> | |||
| <%= render partial: 'admins/projects/shared/list', locals: { projects: @projects } %> | |||
| </div> | |||
| </div> | |||
| <div id="projects-modals"> | |||
| </div> | |||
| @@ -0,0 +1,26 @@ | |||
| <div class="modal fade project-change-modal" tabindex="-1" role="dialog" aria-hidden="true"> | |||
| <div class="modal-dialog modal-dialog-centered" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title"><%= type == "create" ? "新增" : "编辑" %></h5> | |||
| <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||
| <span aria-hidden="true">×</span> | |||
| </button> | |||
| </div> | |||
| <%= form_for @project, url: {controller: "projects", action: "#{type}"} do |p| %> | |||
| <div class="modal-body"> | |||
| <div class="form-group"> | |||
| <label> | |||
| 推荐等级 | |||
| </label> | |||
| <%= p.number_field :recommend_index,class: "form-control input-lg",required: true%> | |||
| </div> | |||
| </div> | |||
| <div class="modal-footer"> | |||
| <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button> | |||
| <%= p.submit "确认", class: "btn btn-primary submit-btn" %> | |||
| </div> | |||
| <% end %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -1,10 +1,12 @@ | |||
| <table class="table table-hover text-center subject-list-table"> | |||
| <thead class="thead-light"> | |||
| <tr> | |||
| <th width="4%">序号</th> | |||
| <th width="4%">ID</th> | |||
| <th width="15%" class="text-left">项目名称</th> | |||
| <th width="6%">序号</th> | |||
| <th width="6%">ID</th> | |||
| <th width="20%" class="text-left">项目名称</th> | |||
| <th width="6%">公开</th> | |||
| <th width="6%">精选</th> | |||
| <th width="6%">推荐</th> | |||
| <th width="5%">issue</th> | |||
| <th width="5%">资源</th> | |||
| <th width="6%">版本库</th> | |||
| @@ -12,8 +14,8 @@ | |||
| <th width="6%">里程碑</th> | |||
| <th width="10%">成员</th> | |||
| <th width="10%">管理员</th> | |||
| <th width="11%"><%= sort_tag('创建时间', name: 'created_on', path: admins_projects_path) %></th> | |||
| <th width="10%">操作</th> | |||
| <th width="15%"><%= sort_tag('创建时间', name: 'created_on', path: admins_projects_path) %></th> | |||
| <th width="25%">操作</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| @@ -26,6 +28,8 @@ | |||
| <%= link_to(project.name, "/projects/#{project&.owner&.login}/#{project.identifier}", target: '_blank') %> | |||
| </td> | |||
| <td><%= project.is_public ? '√' : '' %></td> | |||
| <td><%= project.is_pinned ? '√' : '' %></td> | |||
| <td><%= project.recommend ? '√' : '' %></td> | |||
| <td><%= project.issues.size %></td> | |||
| <td><%= project.attachments.size %></td> | |||
| <td><%= project&.project_score.try(:changeset_num).to_i %></td> | |||
| @@ -37,8 +41,15 @@ | |||
| </td> | |||
| <td><%= project.created_on&.strftime('%Y-%m-%d %H:%M') %></td> | |||
| <td class="action-container"> | |||
| <% if project.is_public %> | |||
| <%= javascript_void_link '精选', class: 'action pinned-action', data: { id: project.id }, style: project.is_pinned ? 'display: none;' : '' %> | |||
| <%= javascript_void_link '取消精选', class: 'action unpinned-action', data: { id: project.id }, style: project.is_pinned ? '' : 'display: none;' %> | |||
| <%= javascript_void_link '推荐', class: 'action recommend-action', data: { id: project.id }, style: project.recommend ? 'display: none;' : '' %> | |||
| <%= javascript_void_link '取消推荐', class: 'action unrecommend-action', data: { id: project.id }, style: project.recommend ? '' : 'display: none;' %> | |||
| <%= link_to "设置推荐等级", edit_admins_project_path(project.id), remote: true, class: "action edit-recommend-action", style: project.recommend ? '' : 'display: none;' %> | |||
| <% end %> | |||
| <%= link_to "删除", admins_project_path(project.id), method: :delete, data:{confirm: "确认删除的吗?"}, class: "delete-project-action" %> | |||
| </td> | |||
| </td> | |||
| </tr> | |||
| <% end %> | |||
| <% else %> | |||
| @@ -1,6 +1,12 @@ | |||
| json.organizations @organizations do |organization| | |||
| json.id organization.id | |||
| json.name organization.login | |||
| json.nickname organization.nickname.blank? ? organization.name : organization.nickname | |||
| json.avatar_url url_to_avatar(organization) | |||
| json.organizations do | |||
| json.array! @organizations.each do |group| | |||
| json.array! group.each do |organization| | |||
| json.id organization.id | |||
| json.name organization.login | |||
| json.nickname organization.real_name | |||
| json.avatar_url url_to_avatar(organization) | |||
| json.website organization.website | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1 @@ | |||
| json.project_categories @project_categories, :id, :name, :logo_url | |||
| @@ -0,0 +1,20 @@ | |||
| project_common = $redis_cache.hgetall("v2-project-common:#{item[0]}") | |||
| owner_common = $redis_cache.hgetall("v2-owner-common:#{project_common["owner_id"]}") | |||
| json.id item[0] | |||
| json.score item[1] | |||
| json.name project_common["name"] | |||
| json.identifier project_common["identifier"] | |||
| json.description project_common["description"] | |||
| json.owner do | |||
| json.id project_common["owner_id"] | |||
| json.type owner_common["type"] | |||
| json.name owner_common["name"] | |||
| json.login owner_common["login"] | |||
| json.avatar_url owner_common["avatar_url"] | |||
| end | |||
| json.visits project_common["visits"] | |||
| json.forks project_common["forks"] | |||
| json.watchers project_common["watchers"] | |||
| json.praises project_common["praises"] | |||
| json.issues project_common["issues"] | |||
| json.pulls project_common["pullrequests"] | |||
| @@ -0,0 +1,8 @@ | |||
| json.partial! "commons/success" | |||
| json.projects do | |||
| json.array! @project_rank.each do |item| | |||
| json.partial! "detail", locals: {item: item} | |||
| end | |||
| end | |||
| @@ -0,0 +1,36 @@ | |||
| json.partial! "commons/success" | |||
| json.projects do | |||
| json.array! @projects do |project| | |||
| owner = project.owner | |||
| json.id project.id | |||
| json.identifier project.identifier | |||
| json.name project.name | |||
| json.description project.description | |||
| json.visits project.visits | |||
| json.author do | |||
| json.name owner.try(:show_real_name) | |||
| json.type owner.type | |||
| json.login owner.login | |||
| json.image_url url_to_avatar(owner) | |||
| end | |||
| json.category do | |||
| if project.project_category.blank? | |||
| json.nil! | |||
| else | |||
| json.id project.project_category.id | |||
| json.name project.project_category.name | |||
| end | |||
| end | |||
| json.language do | |||
| if project.project_language.blank? | |||
| json.nil! | |||
| else | |||
| json.id project.project_language.id | |||
| json.name project.project_language.name | |||
| end | |||
| end | |||
| end | |||
| end | |||
| @@ -12,10 +12,10 @@ json.array! @branches do |branch| | |||
| json.timestamp render_unix_time(branch['commit']['timestamp']) | |||
| json.time_from_now time_from_now(branch['commit']['timestamp']) | |||
| json.author do | |||
| json.partial! 'repositories/commit_author', user: render_commit_author(branch['commit']['author']), name: branch['commit']['author']['name'] | |||
| json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['author']), name: branch['commit']['author']['name'] | |||
| end | |||
| json.committer do | |||
| json.partial! 'repositories/commit_author', user: render_commit_author(branch['commit']['committer']), name: branch['commit']['committer']['name'] | |||
| json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['committer']), name: branch['commit']['committer']['name'] | |||
| end | |||
| end | |||
| end | |||
| @@ -14,10 +14,10 @@ json.array! @branches_slice do |branch_slice| | |||
| json.timestamp render_unix_time(branch['commit']['timestamp']) | |||
| json.time_from_now time_from_now(branch['commit']['timestamp']) | |||
| json.author do | |||
| json.partial! 'repositories/commit_author', user: render_commit_author(branch['commit']['author']), name: branch['commit']['author']['name'] | |||
| json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['author']), name: branch['commit']['author']['name'] | |||
| end | |||
| json.committer do | |||
| json.partial! 'repositories/commit_author', user: render_commit_author(branch['commit']['committer']), name: branch['commit']['committer']['name'] | |||
| json.partial! 'repositories/commit_author', user: render_cache_commit_author(branch['commit']['committer']), name: branch['commit']['committer']['name'] | |||
| end | |||
| end | |||
| end | |||
| @@ -1,4 +1,4 @@ | |||
| json.total_count @total_count | |||
| json.total_count @projects.total_count | |||
| json.projects @projects do |project| | |||
| # json.partial! "/projects/project_detail", project: project | |||
| json.id project.id | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @users.total_count | |||
| json.users do | |||
| json.partial! 'users/user_small', users: @users | |||
| end | |||
| @@ -1,11 +1,9 @@ | |||
| json.author do | |||
| author = User.find_by(mail: commit['Author']['Email']) | |||
| json.partial! 'repositories/commit_author', locals: { user: author, name: commit['Committer']['Name'] } | |||
| json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['Author']), name: commit['Author']['Name'] } | |||
| end | |||
| json.committer do | |||
| author = User.find_by(mail: commit['Committer']['Email']) | |||
| json.partial! 'repositories/commit_author', locals: { user: author, name: commit['Committer']['Name'] } | |||
| json.partial! 'repositories/commit_author', locals: { user: render_cache_commit_author(commit['Committer']), name: commit['Committer']['Name'] } | |||
| end | |||
| json.timestamp render_unix_time(commit['Committer']['When']) | |||
| json.time_from_now time_from_now(commit['Committer']['When']) | |||
| @@ -26,9 +26,9 @@ if @project.forge? | |||
| end | |||
| json.author do | |||
| json.partial! 'commit_author', user: render_commit_author(commit['commit']['author']), name: commit['commit']['author']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(commit['commit']['author']), name: commit['commit']['author']['name'] | |||
| end | |||
| json.committer do | |||
| json.partial! 'commit_author', user: render_commit_author(commit['commit']['committer']), name: commit['commit']['committer']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(commit['commit']['committer']), name: commit['commit']['committer']['name'] | |||
| end | |||
| end | |||
| @@ -1,9 +1,17 @@ | |||
| if user | |||
| json.id user.id | |||
| json.login user.login | |||
| json.name user.real_name | |||
| json.type user&.type | |||
| json.image_url url_to_avatar(user) | |||
| if user.present? | |||
| if user.is_a?(Hash) | |||
| json.id user["id"] | |||
| json.login user["login"] | |||
| json.name user["name"] | |||
| json.type user["type"] | |||
| json.image_url user["avatar_url"] | |||
| else | |||
| json.id user.id | |||
| json.login user.login | |||
| json.name user.real_name | |||
| json.type user&.type | |||
| json.image_url url_to_avatar(user) | |||
| end | |||
| else | |||
| json.id nil | |||
| json.login name | |||
| @@ -0,0 +1,16 @@ | |||
| user = $redis_cache.hgetall("v2-owner-common:#{contributor["login"]}-#{contributor["email"]}") | |||
| if user.blank? | |||
| json.contributions contributor["contributions"] | |||
| # json.gid contributor["id"] | |||
| json.login contributor["login"] | |||
| json.type nil | |||
| json.name contributor["login"] | |||
| json.image_url User::Avatar.get_letter_avatar_url(contributor["login"]) | |||
| else | |||
| json.contributions contributor["contributions"] | |||
| # json.gid contributor["id"] | |||
| json.login user["login"] | |||
| json.type user["type"] | |||
| json.name user["name"] | |||
| json.image_url user["avatar_url"] | |||
| end | |||
| @@ -28,10 +28,10 @@ else | |||
| # end | |||
| # end | |||
| json.author do | |||
| json.partial! 'commit_author', user: render_commit_author(commit['commit']['author']), name: commit['commit']['author']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(commit['commit']['author']), name: commit['commit']['author']['name'] | |||
| end | |||
| json.committer do | |||
| json.partial! 'commit_author', user: render_commit_author(commit['commit']['committer']), name: commit['commit']['committer']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(commit['commit']['committer']), name: commit['commit']['committer']['name'] | |||
| end | |||
| end | |||
| end | |||
| @@ -1,16 +1,6 @@ | |||
| total_count = @contributors.size | |||
| json.contributors @contributors.each do |contributor| | |||
| user = User.find_by(gitea_uid: contributor["id"]) | |||
| if contributor["login"] == "root" | |||
| total_count -= 1 | |||
| next | |||
| end | |||
| json.contributions contributor["contributions"] | |||
| # json.gid contributor["id"] | |||
| json.login user.login | |||
| json.type user&.type | |||
| json.name user.real_name | |||
| json.image_url url_to_avatar(user) | |||
| json.partial! 'contributor', locals: { contributor: contributor } | |||
| end | |||
| json.total_count total_count | |||
| @@ -56,17 +56,7 @@ json.tags_count @result[:branch_tag_total_count]['tag_count'] || 0 | |||
| json.contributors do | |||
| total_count = @result[:contributor].size | |||
| json.list @result[:contributor].each do |contributor| | |||
| user = User.find_by(gitea_uid: contributor["id"]) | |||
| if contributor["login"] == "root" || user.nil? | |||
| total_count -= 1 | |||
| next | |||
| end | |||
| json.contributions contributor["contributions"] | |||
| json.gid contributor["id"] | |||
| json.login user.login | |||
| json.type user&.type | |||
| json.name user.real_name | |||
| json.image_url url_to_avatar(user) | |||
| json.partial! 'contributor', locals: { contributor: contributor } | |||
| end | |||
| json.total_count total_count | |||
| end | |||
| @@ -5,7 +5,7 @@ json.array! @tags do |tag| | |||
| json.zipball_url render_zip_url(@owner, @repository, tag['name']) | |||
| json.tarball_url render_tar_url(@owner, @repository, tag['name']) | |||
| json.tagger do | |||
| json.partial! 'commit_author', user: render_commit_author(tag['tagger']), name: tag['tagger']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(tag['tagger']), name: tag['tagger']['name'] | |||
| end | |||
| json.time_ago time_from_now(tag['tagger']['date'].to_time) | |||
| json.created_at_unix tag['tagger']['date'].to_time.to_i | |||
| @@ -16,10 +16,10 @@ json.array! @tags do |tag| | |||
| json.time_ago time_from_now(tag['commit']['commiter']['date'].to_time) | |||
| json.created_at_unix tag['commit']['commiter']['date'].to_time.to_i | |||
| json.committer do | |||
| json.partial! 'commit_author', user: render_commit_author(tag['commit']['commiter']), name: tag['commit']['commiter']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(tag['commit']['commiter']), name: tag['commit']['commiter']['name'] | |||
| end | |||
| json.author do | |||
| json.partial! 'commit_author', user: render_commit_author(tag['commit']['author']), name: tag['commit']['author']['name'] | |||
| json.partial! 'commit_author', user: render_cache_commit_author(tag['commit']['author']), name: tag['commit']['author']['name'] | |||
| end | |||
| end | |||
| end | |||
| @@ -0,0 +1,21 @@ | |||
| owner_common = $redis_cache.hgetall("v2-owner-common:#{item[0]}") | |||
| deleted_data = $redis_cache.smembers("v2-project-rank-deleted") | |||
| $redis_cache.zrem("v2-user-project-rank:#{item[0]}", delete_data) unless deleted_data.blank? | |||
| popular_project = $redis_cache.zrevrange("v2-user-project-rank:#{item[0]}", 0, 1, withscores: true)[0] | |||
| json.id item[0] | |||
| json.score item[1] | |||
| json.name owner_common["name"] | |||
| json.type owner_common["type"] | |||
| json.login owner_common["login"] | |||
| json.avatar_url owner_common["avatar_url"] | |||
| if popular_project.blank? | |||
| json.project nil | |||
| else | |||
| popular_project_common = $redis_cache.hgetall("v2-project-common:#{popular_project[0]}") | |||
| json.project do | |||
| json.id popular_project[0] | |||
| json.name popular_project_common["name"] | |||
| json.identifier popular_project_common["identifier"] | |||
| json.description popular_project_common["description"] | |||
| end | |||
| end | |||
| @@ -0,0 +1,8 @@ | |||
| json.partial! "commons/success" | |||
| json.users do | |||
| json.array! @user_rank.each do |item| | |||
| json.partial! "detail", locals: {item: item} | |||
| end | |||
| end | |||
| @@ -0,0 +1,16 @@ | |||
| user = $redis_cache.hgetall("v2-owner-common:#{name}-#{email}") | |||
| if user.blank? | |||
| json.id nil | |||
| json.type nil | |||
| json.login name | |||
| json.name name | |||
| json.email email | |||
| json.image_url User::Avatar.get_letter_avatar_url(name) | |||
| else | |||
| json.id user["id"] | |||
| json.type user["type"] | |||
| json.login user["login"] | |||
| json.name user["name"] | |||
| json.email user["email"] | |||
| json.image_url user["avatar_url"] | |||
| end | |||
| @@ -73,6 +73,9 @@ Rails.application.routes.draw do | |||
| resources :public_keys, only: [:index, :create, :destroy] | |||
| resources :project_rank, only: [:index] | |||
| resources :user_rank, only: [:index] | |||
| resources :statistic, only: [:index] do | |||
| collection do | |||
| get :platform_profile | |||
| @@ -156,6 +159,7 @@ Rails.application.routes.draw do | |||
| resources :project_categories, only: [:index, :show] do | |||
| get :group_list, on: :collection | |||
| get :pinned_index, on: :collection | |||
| end | |||
| resources :project_languages, only: [:index, :show] | |||
| resources :ignores, only: [:index, :show] | |||
| @@ -181,6 +185,7 @@ Rails.application.routes.draw do | |||
| post :migrate | |||
| get :group_type_list | |||
| get :recommend | |||
| get :banner_recommend | |||
| end | |||
| end | |||
| @@ -424,7 +429,7 @@ Rails.application.routes.draw do | |||
| get :simple | |||
| get :watchers, to: 'projects#watch_users' | |||
| get :stargazers, to: 'projects#praise_users' | |||
| get :members, to: 'projects#fork_users' | |||
| get :forks, to: 'projects#fork_users' | |||
| match :about, :via => [:get, :put, :post] | |||
| end | |||
| end | |||
| @@ -580,6 +585,7 @@ Rails.application.routes.draw do | |||
| end | |||
| scope module: :projects do | |||
| resources :members, only: [:index] | |||
| resources :teams, only: [:index, :create, :destroy] | |||
| resources :project_units, only: [:index, :create] | |||
| resources :applied_transfer_projects, only: [:create] do | |||
| @@ -904,7 +910,7 @@ Rails.application.routes.draw do | |||
| resources :courses, only: [:index, :destroy, :update] | |||
| resources :projects, only: [:index, :destroy] | |||
| resources :projects, only: [:index, :edit, :update, :destroy] | |||
| resources :disciplines, only: [:index, :create, :edit, :update, :destroy] do | |||
| post :adjust_position, on: :member | |||
| @@ -0,0 +1,7 @@ | |||
| class AddSomeColumnsToProjectDetailFeature < ActiveRecord::Migration[5.2] | |||
| def change | |||
| add_column :project_categories, :pinned_index, :integer, default: 0 | |||
| add_column :projects, :is_pinned, :boolean, default: false | |||
| add_column :projects, :recommend_index, :integer, default: 0 | |||
| end | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| class AddRecommendToOrganizationExtensions < ActiveRecord::Migration[5.2] | |||
| def change | |||
| add_column :organization_extensions, :recommend, :boolean, default: false | |||
| end | |||
| end | |||
| @@ -296,7 +296,7 @@ | |||
| </span> | |||
| </a> | |||
| <div class="toc-wrapper"> | |||
| <img src="images/logo-b38b63e6.png" class="logo" alt="" /> | |||
| <img src="images/logo-cf8353ee.png" class="logo" alt="" /> | |||
| <div class="lang-selector"> | |||
| <a href="#" data-language-name="shell">Shell</a> | |||
| <a href="#" data-language-name="javascript">JavaScript</a> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a>在 {nickname2}/{repository} 指派给你一个易修:<a href="{baseurl}/{login2}/{identifier}/issues/{id}" style="font-weight:bold;color:#3b94d6;">{title}</a><br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -44,16 +44,11 @@ | |||
| {ifduedate}<a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a>将结束日期从 {duedate1} 修改为 {duedate2}{endduedate} | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login}" style="font-weight:bold;color:#3b94d6;">{nickname}</a>已将易修 {title} 删除 | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 你已加入 <a href="{baseurl}/{login}" style="font-weight:bold;color:#3b94d6;">{organization}</a> 组织<br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 你已被移出 <a href="{baseurl}/{login}" style="font-weight:bold;color:#3b94d6;">{organization}</a> 组织<br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 组织 <a href="{baseurl}/{login}" style="font-weight:bold;color:#3b94d6;">{organization}</a> 已把你的角色修改为 {role} | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a>在 {nickname2}/{repository} 新建了一个易修:<a href="{baseurl}/{login2}/{identifier}/issues/{id}" style="font-weight:bold;color:#3b94d6;">{title}</a><br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 你已加入 <a href="{baseurl}/{login}/{identifier}" style="font-weight:bold;color:#3b94d6;">{nickname}/{repository}</a> 项目<br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 你已被移出 <a href="{baseurl}/{login}/{identifier}" style="font-weight:bold;color:#3b94d6;">{nickname}/{repository}</a> 项目<br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a> 已加入项目 <a href="{baseurl}/{login2}/{identifier}" style="font-weight:bold;color:#3b94d6;">{nickname2}/{repository}</a><br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a> 已被移出项目 <a href="{baseurl}/{login2}/{identifier}" style="font-weight:bold;color:#3b94d6;">{nickname2}/{repository}</a><br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a>在 {nickname2}/{repository} 提交了一个合并请求:<a href="{baseurl}/{login2}/{identifier}/pulls/{id}" style="font-weight:bold;color:#3b94d6;">{title}</a><br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 项目 <a href="{baseurl}/{login}" style="font-weight:bold;color:#3b94d6;">{nickname}/{repository}</a> 已把你的角色修改为 {role} | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -41,16 +41,11 @@ | |||
| {ifnavbar}将项目导航更改为"{navbar}"{endnavbar} | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| <a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a>在 {nickname2}/{repository} 指派给你一个合并请求:<a href="{baseurl}/{login2}/{identifier}/pulls/{id}" style="font-weight:bold;color:#3b94d6;">{title}</a><br/> | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -38,16 +38,11 @@ | |||
| {ifpriority}<a href="{baseurl}/{login1}" style="font-weight:bold;color:#3b94d6;">{nickname1}</a>将优先级从 {priority1} 修改为 {priority2}{endpriority} | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 你提交的合并请求:<a href="{baseurl}/{login2}/{identifier}/pulls/{id}" style="font-weight:bold;color:#3b94d6;">{title}</a> 被拒绝; | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||
| @@ -24,8 +24,8 @@ | |||
| <div class="new_content"> | |||
| <div style="width: 598px; background:#fff; margin:20px auto;"> | |||
| <div style="height:50px; width: 578px; background:#3b94d6; padding:9px 10px 6px;border:1px solid #ddd; border-bottom:none;"> | |||
| <a href="https://www.trustie.net/"><img src="http://www.trustie.net/images/nav_logo.png" width="51" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实让创新更美好</p> | |||
| <a href="{baseurl}"><img src="{baseurl}/images/email_logo.png" height="45" ></a> | |||
| <p style="color:#fff; float:right; margin-top:15px;">确实开源,协同创新</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| <div style="width: 558px; border-left:1px solid #ddd;border-right:1px solid #ddd; background:#fff; padding:20px; color:#333; line-height: 1.9;"> | |||
| @@ -34,16 +34,11 @@ | |||
| 你提交的合并请求:<a href="{baseurl}/{login2}/{identifier}/pulls/{id}" style="font-weight:bold;color:#3b94d6;">{title}</a> 已通过; | |||
| </p> | |||
| <div style="width: 100%; border-top: 1px solid #ddd; margin:10px 0;"></div> | |||
| <img src="https://www.trustie.net/images/wechat/trustie_QR.jpg" width="120" height="120" > | |||
| <p style=" color:#666;"> | |||
| 扫一扫,关注trustie微信公众号,更方便获取平台动态,消息推送等提醒<br/> | |||
| 想了解更多信息,请访问 <a href="https://www.trustie.net/" style=" color:#3b94d6;"> www.trustie.net</a> | |||
| </p> | |||
| </div> | |||
| <div style="padding:20px; color:#333; line-height: 1.9;background: #eee;border:1px solid #ddd; border-top:none; width: 558px;"> | |||
| <p style="color:#888; float:left;">如果您在使用中有任何的疑问和建议,欢迎您给我们反馈意见<br/> | |||
| QQ群:1071514693</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">Trustie团队</p> | |||
| <p style="color:#888; float:right;font-weight: bold;font-size: 16px;">GitLink团队</p> | |||
| <div style="clear:both; overflow:hidden;"></div> | |||
| </div> | |||
| </div> | |||