Merge pull request 'CLA 创建系列' (#56) from KingChan/forgeplus:chenjing into standalone_develop
This commit is contained in:
commit
606d6ab621
|
@ -0,0 +1,43 @@
|
|||
$(document).on('turbolinks:load', function(){
|
||||
if ($('body.admins-organizations-index-page').length > 0) {
|
||||
var showSuccessNotify = function() {
|
||||
$.notify({
|
||||
message: '操作成功'
|
||||
},{
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
|
||||
// organizations open cla
|
||||
$('.organizations-list-container').on('click', '.open-cla-action', function(){
|
||||
var $action = $(this);
|
||||
|
||||
var userId = $action.data('id');
|
||||
$.ajax({
|
||||
url: '/admins/organizations/' + userId + '/open_cla',
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
success: function() {
|
||||
showSuccessNotify();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// organizations close cla
|
||||
$('.organizations-list-container').on('click', '.close-cla-action', function(){
|
||||
var $action = $(this);
|
||||
|
||||
var userId = $action.data('id');
|
||||
$.ajax({
|
||||
url: '/admins/organizations/' + userId + '/close_cla',
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
success: function() {
|
||||
showSuccessNotify();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -0,0 +1,2 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -0,0 +1,3 @@
|
|||
// Place all the styles related to the organizations/clas controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -0,0 +1,3 @@
|
|||
// Place all the styles related to the users/clas controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -49,7 +49,7 @@ class Admins::MessageTemplatesController < Admins::BaseController
|
|||
def message_template_params
|
||||
# type = @message_template.present? ? @message_template.type : "MessageTemplate::CustomTip"
|
||||
# params.require(type.split("::").join("_").underscore.to_sym).permit!
|
||||
params.require(:message_template).permit!
|
||||
params.require(:message_template_custom_tip).permit!
|
||||
end
|
||||
|
||||
def get_template
|
||||
|
|
|
@ -9,6 +9,19 @@ class Admins::OrganizationsController < Admins::BaseController
|
|||
@orgs = paginate orgs
|
||||
end
|
||||
|
||||
|
||||
def open_cla
|
||||
@org = Organization.find(params[:id])
|
||||
@org.open_cla!
|
||||
render_ok
|
||||
end
|
||||
|
||||
def close_cla
|
||||
@org = Organization.find(params[:id])
|
||||
@org.close_cla!
|
||||
render_ok
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
class Organizations::ClasController < Organizations::BaseController
|
||||
before_action :load_organization
|
||||
before_action :load_cla, only: [:show, :update, :destroy]
|
||||
def index
|
||||
@cla = @organization.cla
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def create
|
||||
tip_exception("您的组织还未拥有创建CLA权限,请联系管理员") if @organization.cla == false
|
||||
ActiveRecord::Base.transaction do
|
||||
if @organization.cla.present?
|
||||
return tip_exception("组织已存在CLA!")
|
||||
else
|
||||
Organizations::CreateClaForm.new(cla_params).validate!
|
||||
@cla = Cla.build(cla_params)
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
uid_logger_error(e.message)
|
||||
tip_exception(e.message)
|
||||
end
|
||||
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
Organizations::CreateClaForm.new(cla_params).validate!
|
||||
@cla.update(cla_params)
|
||||
end
|
||||
rescue Exception => e
|
||||
uid_logger_error(e.message)
|
||||
tip_exception(e.message)
|
||||
end
|
||||
|
||||
def destroy
|
||||
tip_exception("组织CLA已被签署,无法删除") if @cla.user_clas.size > 0
|
||||
ActiveRecord::Base.transaction do
|
||||
@cla.destroy!
|
||||
end
|
||||
render_ok
|
||||
rescue Exception => e
|
||||
uid_logger_error(e.message)
|
||||
tip_exception(e.message)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def cla_params
|
||||
params.permit(:name, :key, :content, :organization_id, :pr_need)
|
||||
end
|
||||
|
||||
def load_organization
|
||||
@organization = Organization.find_by(login: params[:organization_id]) || Organization.find_by(id: params[:organization_id])
|
||||
return render_not_found("组织不存在") if @organization.nil?
|
||||
return render_forbidden("没有查看组织的权限") if org_limited_condition || org_privacy_condition
|
||||
end
|
||||
|
||||
def load_cla
|
||||
@cla = Cla.find_by!(organization:params[:organization_id], key: params[:id])
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
class Users::ClasController < Users::BaseController
|
||||
def index
|
||||
@user_clas = UserCla.where(user: @_observed_user)
|
||||
end
|
||||
|
||||
def create
|
||||
tip_exception("已签署过该组织CLA!") if @_observed_user.user_clas.where(cla_id: params[:cla_id]).size > 0
|
||||
ActiveRecord::Base.transaction do
|
||||
Users::UserClaForm.new(user_cla_params).validate!
|
||||
@user_cla = UserCla.build(user_cla_params, @_observed_user.id)
|
||||
end
|
||||
rescue Exception => e
|
||||
uid_logger_error(e.message)
|
||||
tip_exception(e.message)
|
||||
end
|
||||
private
|
||||
|
||||
def user_cla_params
|
||||
params.permit(:email, :real_name, :cla_id)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
class Organizations::CreateClaForm < BaseForm
|
||||
KEY_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾
|
||||
attr_accessor :name, :key, :content, :organization_id, :pr_need
|
||||
validates :name, :organization_id , :key, presence: true
|
||||
validates :name, format: { with: KEY_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" }
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
class Users::UserClaForm
|
||||
include ActiveModel::Model
|
||||
attr_accessor :email, :real_name, :cla_id
|
||||
validates :email, presence: true, format: { with: CustomRegexp::EMAIL }
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
module Organizations::ClasHelper
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
module Users::ClasHelper
|
||||
end
|
|
@ -1,41 +1,42 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: attachments
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# container_id :integer
|
||||
# container_type :string(30)
|
||||
# filename :string(255) default(""), not null
|
||||
# disk_filename :string(255) default(""), not null
|
||||
# filesize :integer default("0"), not null
|
||||
# content_type :string(255) default("")
|
||||
# digest :string(60) default(""), not null
|
||||
# downloads :integer default("0"), not null
|
||||
# author_id :integer default("0"), not null
|
||||
# created_on :datetime
|
||||
# description :text(65535)
|
||||
# disk_directory :string(255)
|
||||
# attachtype :integer default("1")
|
||||
# is_public :integer default("1")
|
||||
# copy_from :string(255)
|
||||
# quotes :integer default("0")
|
||||
# is_publish :integer default("1")
|
||||
# publish_time :datetime
|
||||
# resource_bank_id :integer
|
||||
# unified_setting :boolean default("1")
|
||||
# cloud_url :string(255) default("")
|
||||
# course_second_category_id :integer default("0")
|
||||
# delay_publish :boolean default("0")
|
||||
# link :string(255)
|
||||
# clone_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_attachments_on_author_id (author_id)
|
||||
# index_attachments_on_clone_id (clone_id)
|
||||
# index_attachments_on_container_id_and_container_type (container_id,container_type)
|
||||
# index_attachments_on_created_on (created_on)
|
||||
#
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: attachments
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# container_id :integer
|
||||
# container_type :string(30)
|
||||
# filename :string(255) default(""), not null
|
||||
# disk_filename :string(255) default(""), not null
|
||||
# filesize :integer default("0"), not null
|
||||
# content_type :string(255) default("")
|
||||
# digest :string(60) default(""), not null
|
||||
# downloads :integer default("0"), not null
|
||||
# author_id :integer default("0"), not null
|
||||
# created_on :datetime
|
||||
# description :text(65535)
|
||||
# disk_directory :string(255)
|
||||
# attachtype :integer default("1")
|
||||
# is_public :integer default("1")
|
||||
# copy_from :integer
|
||||
# quotes :integer default("0")
|
||||
# is_publish :integer default("1")
|
||||
# publish_time :datetime
|
||||
# resource_bank_id :integer
|
||||
# unified_setting :boolean default("1")
|
||||
# cloud_url :string(255) default("")
|
||||
# course_second_category_id :integer default("0")
|
||||
# delay_publish :boolean default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_attachments_on_author_id (author_id)
|
||||
# index_attachments_on_container_id_and_container_type (container_id,container_type)
|
||||
# index_attachments_on_course_second_category_id (course_second_category_id)
|
||||
# index_attachments_on_created_on (created_on)
|
||||
# index_attachments_on_is_public (is_public)
|
||||
# index_attachments_on_quotes (quotes)
|
||||
#
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -39,15 +39,15 @@
|
|||
# business :boolean default("0")
|
||||
# profile_completed :boolean default("0")
|
||||
# laboratory_id :integer
|
||||
# is_shixun_marker :boolean default("0")
|
||||
# admin_visitable :boolean default("0")
|
||||
# collaborator :boolean default("0")
|
||||
# platform :string(255) default("0")
|
||||
# gitea_token :string(255)
|
||||
# gitea_uid :integer
|
||||
# is_shixun_marker :boolean default("0")
|
||||
# is_sync_pwd :boolean default("1")
|
||||
# watchers_count :integer default("0")
|
||||
# devops_step :integer default("0")
|
||||
# gitea_token :string(255)
|
||||
# platform :string(255)
|
||||
# sign_cla :boolean default("0")
|
||||
# cla :boolean default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
@ -56,8 +56,7 @@
|
|||
# index_users_on_homepage_teacher (homepage_teacher)
|
||||
# index_users_on_laboratory_id (laboratory_id)
|
||||
# index_users_on_login (login) UNIQUE
|
||||
# index_users_on_mail (mail) UNIQUE
|
||||
# index_users_on_phone (phone) UNIQUE
|
||||
# index_users_on_mail (mail)
|
||||
# index_users_on_type (type)
|
||||
#
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: clas
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# name :string(255) not null
|
||||
# key :string(255) not null
|
||||
# content :text(65535)
|
||||
# organization_id :integer not null
|
||||
# pr_need :boolean default("0")
|
||||
# count :integer default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_clas_on_key (key)
|
||||
# index_clas_on_organization_id (organization_id)
|
||||
#
|
||||
|
||||
class Cla < ApplicationRecord
|
||||
has_many :user_clas, :dependent => :destroy
|
||||
has_many :users, through: :user_clas
|
||||
belongs_to :organization
|
||||
|
||||
def to_param
|
||||
self.key.parameterize
|
||||
end
|
||||
|
||||
def self.build(params)
|
||||
self.create!(organization_id: params[:organization_id],
|
||||
name: params[:name],
|
||||
key: params[:key],
|
||||
content: params[:content],
|
||||
pr_need: params[:pr_need]
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,3 +1,26 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: commit_logs
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer
|
||||
# project_id :integer
|
||||
# repository_id :integer
|
||||
# name :string(255)
|
||||
# full_name :string(255)
|
||||
# commit_id :string(255)
|
||||
# ref :string(255)
|
||||
# message :text(65535)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_commit_logs_on_commit_id (commit_id)
|
||||
# index_commit_logs_on_project_id (project_id)
|
||||
# index_commit_logs_on_user_id (user_id)
|
||||
#
|
||||
|
||||
class CommitLog < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: edu_settings
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# name :string(255)
|
||||
# value :string(255)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# description :string(255)
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_edu_settings_on_name (name) UNIQUE
|
||||
#
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: edu_settings
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# name :string(255)
|
||||
# value :string(255)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# description :string(255)
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_edu_settings_on_name (name) UNIQUE
|
||||
#
|
||||
|
||||
|
||||
class EduSetting < ApplicationRecord
|
||||
after_commit :expire_value_cache
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: public_key
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# owner_id :integer not null
|
||||
# name :string(255) not null
|
||||
# fingerprint :string(255) not null
|
||||
# content :text(16777215) not null
|
||||
# mode :integer default("2"), not null
|
||||
# type :integer default("1"), not null
|
||||
# login_source_id :integer default("0"), not null
|
||||
# created_unix :integer
|
||||
# updated_unix :integer
|
||||
# verified :boolean default("0"), not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# IDX_public_key_fingerprint (fingerprint)
|
||||
# IDX_public_key_owner_id (owner_id)
|
||||
#
|
||||
|
||||
class Gitea::PublicKey < Gitea::Base
|
||||
self.inheritance_column = nil # FIX The single-table inheritance mechanism failed
|
||||
# establish_connection :gitea_db
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
# head_branch :string(255)
|
||||
# base_branch :string(255)
|
||||
# merge_base :string(40)
|
||||
# allow_maintainer_edit :boolean default("0"), not null
|
||||
# has_merged :boolean
|
||||
# merged_commit_id :string(40)
|
||||
# merger_id :integer
|
||||
# merged_unix :integer
|
||||
# flow :integer default("0"), not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -1,3 +1,32 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: webhook
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# repo_id :integer
|
||||
# org_id :integer
|
||||
# is_system_webhook :boolean
|
||||
# url :text(65535)
|
||||
# http_method :string(255)
|
||||
# content_type :integer
|
||||
# secret :text(65535)
|
||||
# events :text(65535)
|
||||
# is_active :boolean
|
||||
# type :string(16)
|
||||
# meta :text(65535)
|
||||
# last_status :integer
|
||||
# created_unix :integer
|
||||
# updated_unix :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# IDX_webhook_created_unix (created_unix)
|
||||
# IDX_webhook_is_active (is_active)
|
||||
# IDX_webhook_org_id (org_id)
|
||||
# IDX_webhook_repo_id (repo_id)
|
||||
# IDX_webhook_updated_unix (updated_unix)
|
||||
#
|
||||
|
||||
class Gitea::Webhook < Gitea::Base
|
||||
serialize :events, JSON
|
||||
self.inheritance_column = nil
|
||||
|
@ -10,4 +39,4 @@ class Gitea::Webhook < Gitea::Base
|
|||
enum hook_task_type: {gogs: 1, slack: 2, gitea: 3, discord: 4, dingtalk: 5, telegram: 6, msteams: 7, feishu: 8, matrix: 9}
|
||||
enum last_status: {waiting: 0, succeed: 1, fail: 2}
|
||||
enum content_type: {json: 1, form: 2}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: hook_task
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# hook_id :integer
|
||||
# uuid :string(255)
|
||||
# payload_content :text(4294967295)
|
||||
# event_type :string(255)
|
||||
# is_delivered :boolean
|
||||
# delivered :integer
|
||||
# is_succeed :boolean
|
||||
# request_content :text(4294967295)
|
||||
# response_content :text(4294967295)
|
||||
#
|
||||
|
||||
class Gitea::WebhookTask < Gitea::Base
|
||||
serialize :payload_content, JSON
|
||||
serialize :request_content, JSON
|
||||
|
@ -11,4 +27,4 @@ class Gitea::WebhookTask < Gitea::Base
|
|||
|
||||
enum type: {gogs: 1, slack: 2, gitea: 3, discord: 4, dingtalk: 5, telegram: 6, msteams: 7, feishu: 8, matrix: 9}
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# id :integer not null, primary key
|
||||
# tracker_id :integer not null
|
||||
# project_id :integer not null
|
||||
# subject :string(255) default(""), not null
|
||||
# subject :string(255)
|
||||
# description :text(4294967295)
|
||||
# due_date :date
|
||||
# category_id :integer
|
||||
|
@ -33,6 +33,7 @@
|
|||
# issue_classify :string(255)
|
||||
# ref_name :string(255)
|
||||
# branch_name :string(255)
|
||||
# blockchain_token_num :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: issue_participants
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# issue_id :integer
|
||||
# participant_id :integer
|
||||
# participant_type :integer default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_issue_participants_on_issue_id (issue_id)
|
||||
# index_issue_participants_on_participant_id (participant_id)
|
||||
#
|
||||
|
||||
class IssueParticipant < ApplicationRecord
|
||||
|
||||
belongs_to :issue
|
||||
|
|
|
@ -2,17 +2,18 @@
|
|||
#
|
||||
# Table name: issue_tags
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# name :string(255)
|
||||
# description :string(255)
|
||||
# color :string(255)
|
||||
# user_id :integer
|
||||
# project_id :integer
|
||||
# issues_count :integer default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# gid :integer
|
||||
# gitea_url :string(255)
|
||||
# id :integer not null, primary key
|
||||
# name :string(190)
|
||||
# description :string(255)
|
||||
# color :string(255)
|
||||
# user_id :integer
|
||||
# project_id :integer
|
||||
# issues_count :integer default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# gid :integer
|
||||
# gitea_url :string(255)
|
||||
# pull_requests_count :integer default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
# sync_course :boolean default("0")
|
||||
# sync_subject :boolean default("0")
|
||||
# sync_shixun :boolean default("0")
|
||||
# is_local :boolean default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: mark_files
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# pull_request_id :integer
|
||||
# user_id :integer
|
||||
# file_path_sha :string(255)
|
||||
# file_path :string(255)
|
||||
# mark_as_read :boolean default("0")
|
||||
# updated_after_read :boolean default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_mark_files_on_file_path_sha (file_path_sha)
|
||||
# index_mark_files_on_pull_request_id (pull_request_id)
|
||||
#
|
||||
|
||||
class MarkFile < ApplicationRecord
|
||||
belongs_to :pull_request
|
||||
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: message_templates
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# type :string(255)
|
||||
# sys_notice :text(65535)
|
||||
# email :text(65535)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# notification_url :string(255)
|
||||
# email_title :string(255)
|
||||
#
|
||||
|
||||
class MessageTemplate::IssueCreatorExpire < MessageTemplate
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,11 +46,8 @@
|
|||
# is_sync_pwd :boolean default("1")
|
||||
# watchers_count :integer default("0")
|
||||
# devops_step :integer default("0")
|
||||
# sponsor_certification :integer default("0")
|
||||
# sponsor_num :integer default("0")
|
||||
# sponsored_num :integer default("0")
|
||||
# sponsor_description :text(65535)
|
||||
# award_time :datetime
|
||||
# sign_cla :boolean default("0")
|
||||
# cla :boolean default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
@ -58,7 +55,7 @@
|
|||
# index_users_on_homepage_engineer (homepage_engineer)
|
||||
# index_users_on_homepage_teacher (homepage_teacher)
|
||||
# index_users_on_laboratory_id (laboratory_id)
|
||||
# index_users_on_login (login)
|
||||
# index_users_on_login (login) UNIQUE
|
||||
# index_users_on_mail (mail)
|
||||
# index_users_on_type (type)
|
||||
#
|
||||
|
@ -70,6 +67,8 @@ class Organization < Owner
|
|||
default_scope { where(type: "Organization") }
|
||||
|
||||
has_one :organization_extension, dependent: :destroy
|
||||
has_one :cla, dependent: :destroy
|
||||
|
||||
has_many :teams, dependent: :destroy
|
||||
has_many :organization_users, dependent: :destroy
|
||||
has_many :team_users, dependent: :destroy
|
||||
|
@ -193,4 +192,17 @@ class Organization < Owner
|
|||
name
|
||||
end
|
||||
end
|
||||
|
||||
def open_cla!
|
||||
update_attribute(:cla, true)
|
||||
end
|
||||
|
||||
def close_cla!
|
||||
update_attribute(:cla, false)
|
||||
end
|
||||
|
||||
def open_cla?
|
||||
cla == true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
# news_banner_id :integer
|
||||
# news_content :text(65535)
|
||||
# memo :text(65535)
|
||||
# news_title :string(255)
|
||||
# news_url :string(255)
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
# == 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
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Table name: projects
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# name :string(255)
|
||||
# name :string(190)
|
||||
# description :text(4294967295)
|
||||
# homepage :string(255) default("")
|
||||
# is_public :boolean default("1"), not null
|
||||
|
@ -55,14 +55,17 @@
|
|||
# default_branch :string(255) default("master")
|
||||
# website :string(255)
|
||||
# lesson_url :string(255)
|
||||
# use_blockchain :boolean default("0")
|
||||
# is_pinned :boolean default("0")
|
||||
# recommend_index :integer default("0")
|
||||
# pr_view_admin :boolean default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_projects_on_forked_from_project_id (forked_from_project_id)
|
||||
# index_projects_on_identifier (identifier)
|
||||
# index_projects_on_invite_code (invite_code)
|
||||
# index_projects_on_is_pinned (is_pinned)
|
||||
# index_projects_on_is_public (is_public)
|
||||
# index_projects_on_lft (lft)
|
||||
# index_projects_on_license_id (license_id)
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_repositories_on_identifier (identifier)
|
||||
# index_repositories_on_project_id (project_id)
|
||||
# index_repositories_on_user_id (user_id)
|
||||
#
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
#
|
||||
# Table name: system_notification_histories
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# system_message_id :integer
|
||||
# user_id :integer
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# id :integer not null, primary key
|
||||
# system_notification_id :integer
|
||||
# user_id :integer
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_system_notification_histories_on_system_message_id (system_message_id)
|
||||
# index_system_notification_histories_on_user_id (user_id)
|
||||
# index_system_notification_histories_on_system_notification_id (system_notification_id)
|
||||
# index_system_notification_histories_on_user_id (user_id)
|
||||
#
|
||||
|
||||
class SystemNotificationHistory < ApplicationRecord
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
# type :string(255)
|
||||
# name :string(255)
|
||||
# key :string(255)
|
||||
# openning :boolean
|
||||
# notification_disabled :boolean
|
||||
# email_disabled :boolean
|
||||
# openning :boolean default("1")
|
||||
# notification_disabled :boolean default("1")
|
||||
# email_disabled :boolean default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
# type :string(255)
|
||||
# name :string(255)
|
||||
# key :string(255)
|
||||
# openning :boolean
|
||||
# notification_disabled :boolean
|
||||
# email_disabled :boolean
|
||||
# openning :boolean default("1")
|
||||
# notification_disabled :boolean default("1")
|
||||
# email_disabled :boolean default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
# type :string(255)
|
||||
# name :string(255)
|
||||
# key :string(255)
|
||||
# openning :boolean
|
||||
# notification_disabled :boolean
|
||||
# email_disabled :boolean
|
||||
# openning :boolean default("1")
|
||||
# notification_disabled :boolean default("1")
|
||||
# email_disabled :boolean default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
# type :string(255)
|
||||
# name :string(255)
|
||||
# key :string(255)
|
||||
# openning :boolean
|
||||
# notification_disabled :boolean
|
||||
# email_disabled :boolean
|
||||
# openning :boolean default("1")
|
||||
# notification_disabled :boolean default("1")
|
||||
# email_disabled :boolean default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
# type :string(255)
|
||||
# name :string(255)
|
||||
# key :string(255)
|
||||
# openning :boolean
|
||||
# notification_disabled :boolean
|
||||
# email_disabled :boolean
|
||||
# openning :boolean default("1")
|
||||
# notification_disabled :boolean default("1")
|
||||
# email_disabled :boolean default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# id :integer not null, primary key
|
||||
# time :string(255)
|
||||
# project_id :integer
|
||||
# visits :integer
|
||||
# visits :integer default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: tokens
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer default("0"), not null
|
||||
# action :string(30) default(""), not null
|
||||
# value :string(40) default(""), not null
|
||||
# created_on :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_tokens_on_user_id (user_id)
|
||||
# tokens_value (value) UNIQUE
|
||||
#
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: tokens
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer default("0"), not null
|
||||
# action :string(30) default(""), not null
|
||||
# value :string(40) default(""), not null
|
||||
# created_on :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_tokens_on_user_id (user_id)
|
||||
# tokens_value (value) UNIQUE
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
@ -14,4 +13,4 @@
|
|||
# GLCC 新闻稿
|
||||
class Topic::GlccNews < Topic
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# type :string(255)
|
||||
# title :string(255)
|
||||
# uuid :integer
|
||||
# image_url :string(255)
|
||||
# url :string(255)
|
||||
# order_index :integer
|
||||
#
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
# watchers_count :integer default("0")
|
||||
# devops_step :integer default("0")
|
||||
# sign_cla :boolean default("0")
|
||||
# cla :boolean default("0")
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
@ -181,6 +182,10 @@ class User < Owner
|
|||
has_many :issue_participants, foreign_key: :participant_id
|
||||
has_many :participant_issues, through: :issue_participants, source: :issue
|
||||
has_many :project_topics
|
||||
#cla
|
||||
has_many :user_clas, :dependent => :destroy
|
||||
has_many :clas, through: :user_clas
|
||||
|
||||
# Groups and active users
|
||||
scope :active, lambda { where(status: [STATUS_ACTIVE, STATUS_EDIT_INFO]) }
|
||||
scope :like, lambda { |keywords|
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_user_actions_on_ip (ip)
|
||||
# index_user_actions_on_user_id (user_id)
|
||||
# index_user_actions_on_user_id_and_action_type (user_id,action_type)
|
||||
# index_user_actions_on_action_id (action_id)
|
||||
# index_user_actions_on_action_type (action_type)
|
||||
# index_user_actions_on_ip (ip)
|
||||
# index_user_actions_on_user_id (user_id)
|
||||
#
|
||||
|
||||
class UserAction < ApplicationRecord
|
||||
|
|
|
@ -10,13 +10,10 @@
|
|||
# updated_at :datetime not null
|
||||
# register_status :integer default("0")
|
||||
# action_status :integer default("0")
|
||||
# is_delete :boolean default("0")
|
||||
# user_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_user_agents_on_ip (ip)
|
||||
# index_user_agents_on_user_id (user_id)
|
||||
# index_user_agents_on_ip (ip) UNIQUE
|
||||
#
|
||||
|
||||
class UserAgent < ApplicationRecord
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: user_clas
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer not null
|
||||
# cla_id :integer not null
|
||||
# real_name :string(255) not null
|
||||
# email :string(255) not null
|
||||
# state :integer default("0")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_user_clas_on_cla_id (cla_id)
|
||||
# index_user_clas_on_user_id (user_id)
|
||||
#
|
||||
|
||||
class UserCla < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :cla
|
||||
# identity 0: 教师教授 1: 学生, 2: 专业人士, 3: 开发者
|
||||
enum state: { deafult: 0, signed: 1, failed: 2}
|
||||
|
||||
def self.build(params,user_id)
|
||||
self.create!(user_id: user_id,
|
||||
cla_id: params[:cla_id],
|
||||
real_name: params[:real_name],
|
||||
email: params[:email],
|
||||
state: 1
|
||||
)
|
||||
end
|
||||
|
||||
end
|
|
@ -20,7 +20,7 @@
|
|||
# student_realname :string(255)
|
||||
# location_city :string(255)
|
||||
# school_id :integer
|
||||
# description :string(255) default("")
|
||||
# description :string(255)
|
||||
# department_id :integer
|
||||
# province :string(255)
|
||||
# city :string(255)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# id :integer not null, primary key
|
||||
# project_id :integer default("0"), not null
|
||||
# name :string(255) default(""), not null
|
||||
# name :string(255)
|
||||
# description :text(65535)
|
||||
# effective_date :date
|
||||
# created_on :datetime
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer
|
||||
# name :string(255)
|
||||
# name :text(4294967295)
|
||||
# body :text(65535)
|
||||
# tag_name :string(255)
|
||||
# tag_name :text(65535)
|
||||
# target_commitish :string(255)
|
||||
# draft :boolean default("0")
|
||||
# prerelease :boolean default("0")
|
||||
|
|
|
@ -27,11 +27,12 @@
|
|||
<td><%= link_to org.organization_users_count, "/#{org.login}", target: "_blank" %></td>
|
||||
<td><%= link_to org.projects_count, "/#{org.login}", target: "_blank" %></td>
|
||||
<td class="action-container">
|
||||
<%= javascript_void_link '开通CLA', class: 'action open-cla-action', data: { id: org.id }, style: org.open_cla? ? 'display: none;' : '' %>
|
||||
<%= javascript_void_link '关闭CLA', class: 'action close-cla-action', data: { id: org.id }, style: org.open_cla? ? '' : 'display: none;' %>
|
||||
<%= link_to '查看', admins_organization_path(org), class: 'action' %>
|
||||
<div class="d-inline">
|
||||
<%= javascript_void_link('更多', class: 'action dropdown-toggle', 'data-toggle': 'dropdown', 'aria-haspopup': true, 'aria-expanded': false) %>
|
||||
<div class="dropdown-menu more-action-dropdown">
|
||||
|
||||
<%= delete_link '删除', admins_organization_path(org, element: ".user-item-#{org.id}"), class: 'dropdown-item delete-user-action' %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
<%= f.input :lastname, label: '姓名', wrapper_html: { class: 'col-md-3' }, input_html: { readonly: true, class: 'col-md-11', value: @org.real_name } %>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<%= f.input :cla, label: '是否开通CLA', wrapper_html: { class: 'col-md-3' }, input_html: { class: 'col-md-11', value: @org.cla } %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
json.id cla.id
|
||||
json.content cla.content
|
||||
json.key cla.key
|
||||
json.name cla.name
|
||||
json.pr_need cla.pr_need
|
|
@ -0,0 +1 @@
|
|||
json.partial! "detail", cla: @cla, organization: @organization
|
|
@ -0,0 +1,4 @@
|
|||
json.id @cla.id
|
||||
json.key @cla.key
|
||||
json.name @cla.name
|
||||
json.pr_need @cla.pr_need
|
|
@ -0,0 +1,6 @@
|
|||
json.partial! "detail", cla: @cla, organization: @organization
|
||||
json.is_admin @is_admin
|
||||
json.is_member @is_member
|
||||
json.organization do
|
||||
json.partial! "organizations/organizations/simple", organization: @organization
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
json.partial! "detail", cla: @cla, organization: @organization
|
|
@ -16,4 +16,5 @@ json.news_banner_id organization.news_banner_id
|
|||
json.news_content organization.news_content
|
||||
json.memo organization.memo
|
||||
json.news_title organization.news_title
|
||||
json.news_url organization.news_url
|
||||
json.news_url organization.news_url
|
||||
json.cla organization.cla
|
|
@ -0,0 +1,8 @@
|
|||
json.id user_cla.id
|
||||
json.real_name user_cla.real_name
|
||||
json.email user_cla.email
|
||||
json.state user_cla.state
|
||||
json.created_at format_time(user_cla.created_at)
|
||||
json.cla do
|
||||
json.partial! "/organizations/clas/detail", locals: {cla: user_cla.cla}
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
json.user_clas @user_clas do |user_cla|
|
||||
json.partial! "detail", user_cla: user_cla
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
json.partial! "detail", user_cla: @user_cla
|
|
@ -147,6 +147,7 @@ Rails.application.routes.draw do
|
|||
delete :quit
|
||||
end
|
||||
end
|
||||
resources :clas
|
||||
resources :teams, except: [:edit, :new] do
|
||||
collection do
|
||||
get :search
|
||||
|
@ -389,7 +390,7 @@ Rails.application.routes.draw do
|
|||
|
||||
scope module: :users do
|
||||
resource :interest, only: [:create]
|
||||
|
||||
resources :clas, only: [:index,:create]
|
||||
resources :accounts, only: [:show, :update] do
|
||||
resource :phone_bind, only: [:create]
|
||||
resource :email_bind, only: [:create]
|
||||
|
@ -847,7 +848,12 @@ Rails.application.routes.draw do
|
|||
resources :school_statistics, only: [:index] do
|
||||
get :contrast, on: :collection
|
||||
end
|
||||
resources :organizations, only: [:index, :edit, :show, :destroy]
|
||||
resources :organizations, only: [:index, :edit, :show, :destroy] do
|
||||
member do
|
||||
post :open_cla
|
||||
post :close_cla
|
||||
end
|
||||
end
|
||||
resources :users, only: [:index, :edit, :update, :destroy] do
|
||||
member do
|
||||
post :reward_grade
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddClaToUsers < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :users, :cla, :boolean, default: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class CreateClas < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :clas do |t|
|
||||
t.string :name, null:false
|
||||
t.string :key, index:true, null:false
|
||||
t.text :content
|
||||
t.integer :organization_id, index:true, null:false
|
||||
t.boolean :pr_need, default: false
|
||||
t.integer :count, default: 0
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
class CreateUserClas < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :user_clas do |t|
|
||||
t.integer :user_id, null:false, index: true
|
||||
t.integer :cla_id, null:false, index: true
|
||||
t.string :real_name, null:false
|
||||
t.string :email, null:false
|
||||
t.integer :state, default:0
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Organizations::ClasController, type: :controller do
|
||||
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Users::ClasController, type: :controller do
|
||||
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
require 'rails_helper'
|
||||
|
||||
# Specs in this file have access to a helper object that includes
|
||||
# the Organizations::ClasHelper. For example:
|
||||
#
|
||||
# describe Organizations::ClasHelper do
|
||||
# describe "string concat" do
|
||||
# it "concats two strings with spaces" do
|
||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
RSpec.describe Organizations::ClasHelper, type: :helper do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
require 'rails_helper'
|
||||
|
||||
# Specs in this file have access to a helper object that includes
|
||||
# the Users::ClasHelper. For example:
|
||||
#
|
||||
# describe Users::ClasHelper do
|
||||
# describe "string concat" do
|
||||
# it "concats two strings with spaces" do
|
||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
RSpec.describe Users::ClasHelper, type: :helper do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Cla, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UserCla, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
Loading…
Reference in New Issue