Thorsten Eckel 2019-07-04 13:16:55 +02:00
parent c788c3e053
commit 5745fa46bd
70 changed files with 648 additions and 316 deletions

View file

@ -268,3 +268,11 @@ Layout/MultilineMethodCallIndentation:
Style/HashSyntax: Style/HashSyntax:
Exclude: Exclude:
- "**/*.rake" - "**/*.rake"
Rails/Exit:
Exclude:
- "config/initializers/*.rb"
Rails/Output:
Exclude:
- "config/initializers/*.rb"

View file

@ -2,11 +2,12 @@ source 'https://rubygems.org'
# core - base # core - base
ruby '2.5.5' ruby '2.5.5'
gem 'rails', '5.1.7' gem 'rails', '5.2.3'
# core - rails additions # core - rails additions
gem 'activerecord-import' gem 'activerecord-import'
gem 'activerecord-session_store' gem 'activerecord-session_store'
gem 'bootsnap', require: false
gem 'composite_primary_keys' gem 'composite_primary_keys'
gem 'json' gem 'json'
gem 'rails-observers' gem 'rails-observers'

View file

@ -49,39 +49,39 @@ GEM
specs: specs:
aasm (5.0.0) aasm (5.0.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
actioncable (5.1.7) actioncable (5.2.3)
actionpack (= 5.1.7) actionpack (= 5.2.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (~> 0.6.1) websocket-driver (>= 0.6.1)
actionmailer (5.1.7) actionmailer (5.2.3)
actionpack (= 5.1.7) actionpack (= 5.2.3)
actionview (= 5.1.7) actionview (= 5.2.3)
activejob (= 5.1.7) activejob (= 5.2.3)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.1.7) actionpack (5.2.3)
actionview (= 5.1.7) actionview (= 5.2.3)
activesupport (= 5.1.7) activesupport (= 5.2.3)
rack (~> 2.0) rack (~> 2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.1.7) actionview (5.2.3)
activesupport (= 5.1.7) activesupport (= 5.2.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.1.7) activejob (5.2.3)
activesupport (= 5.1.7) activesupport (= 5.2.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.1.7) activemodel (5.2.3)
activesupport (= 5.1.7) activesupport (= 5.2.3)
activerecord (5.1.7) activerecord (5.2.3)
activemodel (= 5.1.7) activemodel (= 5.2.3)
activesupport (= 5.1.7) activesupport (= 5.2.3)
arel (~> 8.0) arel (>= 9.0)
activerecord-import (1.0.1) activerecord-import (1.0.2)
activerecord (>= 3.2) activerecord (>= 3.2)
activerecord-nulldb-adapter (0.3.9) activerecord-nulldb-adapter (0.3.9)
activerecord (>= 2.0.0) activerecord (>= 2.0.0)
@ -91,16 +91,20 @@ GEM
multi_json (~> 1.11, >= 1.11.2) multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3) rack (>= 1.5.2, < 3)
railties (>= 4.0) railties (>= 4.0)
activesupport (5.1.7) activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
marcel (~> 0.3.1)
activesupport (5.2.3)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts_as_list (0.9.16) acts_as_list (0.9.19)
activerecord (>= 3.0) activerecord (>= 3.0)
addressable (2.5.2) addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0) public_suffix (>= 2.0.2, < 4.0)
arel (8.0.0) arel (9.0.0)
argon2 (2.0.2) argon2 (2.0.2)
ffi (~> 1.9) ffi (~> 1.9)
ffi-compiler (>= 0.1) ffi-compiler (>= 0.1)
@ -112,6 +116,8 @@ GEM
biz (1.8.2) biz (1.8.2)
clavius (~> 1.0) clavius (~> 1.0)
tzinfo tzinfo
bootsnap (1.3.2)
msgpack (~> 1.0)
browser (2.5.3) browser (2.5.3)
buftok (0.2.0) buftok (0.2.0)
builder (3.2.3) builder (3.2.3)
@ -141,8 +147,8 @@ GEM
coffee-script coffee-script
execjs execjs
json json
composite_primary_keys (10.0.5) composite_primary_keys (11.2.0)
activerecord (~> 5.1.0, >= 5.1.6) activerecord (~> 5.2.1)
concurrent-ruby (1.1.5) concurrent-ruby (1.1.5)
coveralls (0.8.23) coveralls (0.8.23)
json (>= 1.8, < 3) json (>= 1.8, < 3)
@ -156,7 +162,7 @@ GEM
daemons (1.3.1) daemons (1.3.1)
dalli (2.7.10) dalli (2.7.10)
debug_inspector (0.0.3) debug_inspector (0.0.3)
delayed_job (4.1.5) delayed_job (4.1.7)
activesupport (>= 3.0, < 5.3) activesupport (>= 3.0, < 5.3)
delayed_job_active_record (4.1.3) delayed_job_active_record (4.1.3)
activerecord (>= 3.0, < 5.3) activerecord (>= 3.0, < 5.3)
@ -268,17 +274,21 @@ GEM
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.0.13) lumberjack (1.0.13)
marcel (0.3.3)
mimemagic (~> 0.3.2)
memoizable (0.4.2) memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.2) method_source (0.9.2)
mime-types (3.2.2) mime-types (3.2.2)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2019.0331) mime-types-data (3.2019.0331)
mimemagic (0.3.3)
mini_mime (1.0.1) mini_mime (1.0.1)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
mini_racer (0.2.4) mini_racer (0.2.4)
libv8 (>= 6.3) libv8 (>= 6.3)
minitest (5.11.3) minitest (5.11.3)
msgpack (1.2.4)
multi_json (1.13.1) multi_json (1.13.1)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.1.1) multipart-post (2.1.1)
@ -366,17 +376,18 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.1.7) rails (5.2.3)
actioncable (= 5.1.7) actioncable (= 5.2.3)
actionmailer (= 5.1.7) actionmailer (= 5.2.3)
actionpack (= 5.1.7) actionpack (= 5.2.3)
actionview (= 5.1.7) actionview (= 5.2.3)
activejob (= 5.1.7) activejob (= 5.2.3)
activemodel (= 5.1.7) activemodel (= 5.2.3)
activerecord (= 5.1.7) activerecord (= 5.2.3)
activesupport (= 5.1.7) activestorage (= 5.2.3)
activesupport (= 5.2.3)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.1.7) railties (= 5.2.3)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4) rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x) actionpack (>= 5.0.1.x)
@ -389,12 +400,12 @@ GEM
loofah (~> 2.2, >= 2.2.2) loofah (~> 2.2, >= 2.2.2)
rails-observers (0.1.5) rails-observers (0.1.5)
activemodel (>= 4.0) activemodel (>= 4.0)
railties (5.1.7) railties (5.2.3)
actionpack (= 5.1.7) actionpack (= 5.2.3)
activesupport (= 5.1.7) activesupport (= 5.2.3)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.19.0, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
raindrops (0.19.0) raindrops (0.19.0)
rake (12.3.2) rake (12.3.2)
@ -522,9 +533,9 @@ GEM
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff hashdiff
websocket-driver (0.6.5) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3) websocket-extensions (0.1.4)
writeexcel (1.0.5) writeexcel (1.0.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -548,6 +559,7 @@ DEPENDENCIES
autodiscover! autodiscover!
autoprefixer-rails autoprefixer-rails
biz biz
bootsnap
browser browser
byebug byebug
capybara capybara
@ -602,7 +614,7 @@ DEPENDENCIES
pry-stack_explorer pry-stack_explorer
puma puma
rack-livereload rack-livereload
rails (= 5.1.7) rails (= 5.2.3)
rails-controller-testing rails-controller-testing
rails-observers rails-observers
rb-fsevent rb-fsevent

View file

@ -2,6 +2,9 @@ module ApplicationController::PreventsCsrf
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
# disable Rails default (>= 5.2) CSRF checks
skip_before_action :verify_authenticity_token, raise: false
before_action :verify_csrf_token before_action :verify_csrf_token
after_action :set_csrf_token_headers after_action :set_csrf_token_headers
end end

View file

@ -102,7 +102,7 @@ curl http://localhost/api/v1/monitoring/health_check?token=XXX
handler_attempts_map = {} handler_attempts_map = {}
failed_jobs.order(:created_at).limit(10).each do |job| failed_jobs.order(:created_at).limit(10).each do |job|
job_name = if job.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'.freeze job_name = if job.class.name == 'Delayed::Backend::ActiveRecord::Job'.freeze && job.payload_object.respond_to?(:job_data)
job.payload_object.job_data['job_class'] job.payload_object.job_data['job_class']
else else
job.name job.name

View file

@ -9,12 +9,9 @@ class ApplicationJob < ActiveJob::Base
# e.g. in the MonitoringController. # e.g. in the MonitoringController.
# This is a workaround to sync ActiveJob#executions to Delayed::Job#attempts # This is a workaround to sync ActiveJob#executions to Delayed::Job#attempts
# until we resolve this dependency. # until we resolve this dependency.
around_enqueue do |job, block| after_enqueue do |job|
block.call.tap do |delayed_job| # update the column right away without loading Delayed::Job record
# skip test adapter # see: https://stackoverflow.com/a/34264580
break if delayed_job.is_a?(Array) Delayed::Job.where(id: job.provider_job_id).update_all(attempts: job.executions) # rubocop:disable Rails/SkipsModelValidations
delayed_job.update!(attempts: job.executions)
end
end end
end end

View file

@ -5,8 +5,8 @@ class ActivityStream < ApplicationModel
self.table_name = 'activity_streams' self.table_name = 'activity_streams'
# rubocop:disable Rails/InverseOf # rubocop:disable Rails/InverseOf
belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'activity_stream_object_id' belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'activity_stream_object_id', optional: true
belongs_to :type, class_name: 'TypeLookup', foreign_key: 'activity_stream_type_id' belongs_to :type, class_name: 'TypeLookup', foreign_key: 'activity_stream_type_id', optional: true
# rubocop:enable Rails/InverseOf # rubocop:enable Rails/InverseOf
# the noop is needed since Layout/EmptyLines detects # the noop is needed since Layout/EmptyLines detects

View file

@ -134,7 +134,7 @@ returns
next if association_attributes_ignored.include?(assoc_name) next if association_attributes_ignored.include?(assoc_name)
eager_load.push(assoc_name) eager_load.push(assoc_name)
pluck.push("#{ActiveRecord::Base.connection.quote_table_name(assoc.table_name)}.id AS #{ActiveRecord::Base.connection.quote_table_name(assoc_name)}") pluck.push(Arel.sql("#{ActiveRecord::Base.connection.quote_table_name(assoc.table_name)}.id AS #{ActiveRecord::Base.connection.quote_table_name(assoc_name)}"))
keys.push("#{assoc_name.to_s.singularize}_ids") keys.push("#{assoc_name.to_s.singularize}_ids")
end end

View file

@ -1,7 +1,7 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Authorization < ApplicationModel class Authorization < ApplicationModel
belongs_to :user belongs_to :user, optional: true
after_create :delete_user_cache after_create :delete_user_cache
after_update :delete_user_cache after_update :delete_user_cache
after_destroy :delete_user_cache after_destroy :delete_user_cache

View file

@ -1,7 +1,7 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Avatar < ApplicationModel class Avatar < ApplicationModel
belongs_to :object_lookup belongs_to :object_lookup, optional: true
=begin =begin

View file

@ -3,7 +3,7 @@
class Channel < ApplicationModel class Channel < ApplicationModel
include Channel::Assets include Channel::Assets
belongs_to :group belongs_to :group, optional: true
store :options store :options
store :preferences store :preferences

View file

@ -31,7 +31,7 @@ module CanBePublished
local = "#{scope_name}_by".to_sym local = "#{scope_name}_by".to_sym
remote = inverse_relation_name(scope_name).to_sym remote = inverse_relation_name(scope_name).to_sym
belongs_to local, class_name: 'User', inverse_of: remote belongs_to local, class_name: 'User', inverse_of: remote, optional: true
User.has_many remote, class_name: model_name, inverse_of: local, foreign_key: "#{local}_id" User.has_many remote, class_name: model_name, inverse_of: local, foreign_key: "#{local}_id"
User.association_attributes_ignored remote User.association_attributes_ignored remote

View file

@ -7,8 +7,8 @@ module HasGroupRelationDefinition
self.table_name = "groups_#{group_relation_model_identifier}s" self.table_name = "groups_#{group_relation_model_identifier}s"
self.primary_keys = ref_key, :group_id, :access self.primary_keys = ref_key, :group_id, :access
belongs_to group_relation_model_identifier belongs_to group_relation_model_identifier, optional: true
belongs_to :group belongs_to :group, optional: true
validates :access, presence: true validates :access, presence: true
validate :validate_access validate :validate_access

View file

@ -72,7 +72,7 @@ module HasGroups
access = self.class.ensure_group_access_list_parameter(access) access = self.class.ensure_group_access_list_parameter(access)
# check direct access # check direct access
return true if group_through.klass.includes(:group).exists?( return true if group_through.klass.eager_load(:group).exists?(
group_through.foreign_key => id, group_through.foreign_key => id,
group_id: group_id, group_id: group_id,
access: access, access: access,
@ -108,13 +108,13 @@ module HasGroups
klass = group_through.klass klass = group_through.klass
# check direct access # check direct access
ids = klass.includes(:group).where(foreign_key => id, access: access, groups: { active: true }).pluck(:group_id) ids = klass.eager_load(:group).where(foreign_key => id, access: access, groups: { active: true }).pluck(:group_id)
ids ||= [] ids ||= []
# check indirect access through roles if possible # check indirect access through roles if possible
return ids if !respond_to?(:role_ids) return ids if !respond_to?(:role_ids)
role_group_ids = RoleGroup.includes(:group).where(role_id: role_ids, access: access, groups: { active: true }).pluck(:group_id) role_group_ids = RoleGroup.eager_load(:group).where(role_id: role_ids, access: access, groups: { active: true }).pluck(:group_id)
# combines and removes duplicates # combines and removes duplicates
# and returns them in one statement # and returns them in one statement

View file

@ -23,7 +23,7 @@ module HasRoles
group_id = self.class.ensure_group_id_parameter(group_id) group_id = self.class.ensure_group_id_parameter(group_id)
access = self.class.ensure_group_access_list_parameter(access) access = self.class.ensure_group_access_list_parameter(access)
RoleGroup.includes(:group, :role).exists?( RoleGroup.eager_load(:group, :role).exists?(
role_id: roles.pluck(:id), role_id: roles.pluck(:id),
group_id: group_id, group_id: group_id,
access: access, access: access,
@ -58,7 +58,7 @@ module HasRoles
group_id = ensure_group_id_parameter(group_id) group_id = ensure_group_id_parameter(group_id)
access = ensure_group_access_list_parameter(access) access = ensure_group_access_list_parameter(access)
role_ids = RoleGroup.includes(:role).where(group_id: group_id, access: access, roles: { active: true }).pluck(:role_id) role_ids = RoleGroup.eager_load(:role).where(group_id: group_id, access: access, roles: { active: true }).pluck(:role_id)
join_table = reflect_on_association(:roles).join_table join_table = reflect_on_association(:roles).join_table
joins(:roles).where(active: true, join_table => { role_id: role_ids }).distinct.select(&:groups_access_permission?) joins(:roles).where(active: true, join_table => { role_id: role_ids }).distinct.select(&:groups_access_permission?)
end end

View file

@ -53,7 +53,7 @@ returns
Cti::CallerId.select('MAX(id) as caller_id') Cti::CallerId.select('MAX(id) as caller_id')
.where({ caller_id: caller_id, level: level }.compact) .where({ caller_id: caller_id, level: level }.compact)
.group(:user_id) .group(:user_id)
.order('caller_id DESC') # not used as `caller_id: :desc` because is needed for `as caller_id` .order(Arel.sql('caller_id DESC')) # not used as `caller_id: :desc` because is needed for `as caller_id`
.limit(20) .limit(20)
.map(&:caller_id) .map(&:caller_id)
end.find(&:present?) end.find(&:present?)

View file

@ -4,7 +4,7 @@ class EmailAddress < ApplicationModel
include ChecksLatestChangeObserved include ChecksLatestChangeObserved
has_many :groups, after_add: :cache_update, after_remove: :cache_update has_many :groups, after_add: :cache_update, after_remove: :cache_update
belongs_to :channel belongs_to :channel, optional: true
validates :realname, presence: true validates :realname, presence: true
validates :email, presence: true validates :email, presence: true

View file

@ -8,8 +8,8 @@ class Group < ApplicationModel
include HasHistory include HasHistory
include HasObjectManagerAttributesValidation include HasObjectManagerAttributesValidation
belongs_to :email_address belongs_to :email_address, optional: true
belongs_to :signature belongs_to :signature, optional: true
validates :name, presence: true validates :name, presence: true

View file

@ -6,9 +6,9 @@ class History < ApplicationModel
self.table_name = 'histories' self.table_name = 'histories'
belongs_to :history_type, class_name: 'History::Type' belongs_to :history_type, class_name: 'History::Type', optional: true
belongs_to :history_object, class_name: 'History::Object' belongs_to :history_object, class_name: 'History::Object', optional: true
belongs_to :history_attribute, class_name: 'History::Attribute' belongs_to :history_attribute, class_name: 'History::Attribute', optional: true
# the noop is needed since Layout/EmptyLines detects # the noop is needed since Layout/EmptyLines detects
# the block commend below wrongly as the measurement of # the block commend below wrongly as the measurement of

View file

@ -1,7 +1,7 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Karma::ActivityLog < ApplicationModel class Karma::ActivityLog < ApplicationModel
belongs_to :object_lookup belongs_to :object_lookup, optional: true
self.table_name = 'karma_activity_logs' self.table_name = 'karma_activity_logs'

View file

@ -22,7 +22,8 @@ class KnowledgeBase::Category < ApplicationModel
belongs_to :parent, class_name: 'KnowledgeBase::Category', belongs_to :parent, class_name: 'KnowledgeBase::Category',
foreign_key: :parent_id, foreign_key: :parent_id,
inverse_of: :children, inverse_of: :children,
touch: true touch: true,
optional: true
validates :category_icon, presence: true validates :category_icon, presence: true

View file

@ -2,8 +2,8 @@
class Link < ApplicationModel class Link < ApplicationModel
belongs_to :link_type, class_name: 'Link::Type' belongs_to :link_type, class_name: 'Link::Type', optional: true
belongs_to :link_object, class_name: 'Link::Object' belongs_to :link_object, class_name: 'Link::Object', optional: true
after_destroy :touch_link_references after_destroy :touch_link_references

View file

@ -22,7 +22,7 @@ class ObjectManager::Attribute < ApplicationModel
self.table_name = 'object_manager_attributes' self.table_name = 'object_manager_attributes'
belongs_to :object_lookup belongs_to :object_lookup, optional: true
validates :name, presence: true validates :name, presence: true
validates :data_type, inclusion: { in: DATA_TYPES, msg: '%{value} is not a valid data type' } validates :data_type, inclusion: { in: DATA_TYPES, msg: '%{value} is not a valid data type' }

View file

@ -3,10 +3,10 @@
class OnlineNotification < ApplicationModel class OnlineNotification < ApplicationModel
include OnlineNotification::Assets include OnlineNotification::Assets
belongs_to :user belongs_to :user, optional: true
# rubocop:disable Rails/InverseOf # rubocop:disable Rails/InverseOf
belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'object_lookup_id' belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'object_lookup_id', optional: true
belongs_to :type, class_name: 'TypeLookup', foreign_key: 'type_lookup_id' belongs_to :type, class_name: 'TypeLookup', foreign_key: 'type_lookup_id', optional: true
# rubocop:enable Rails/InverseOf # rubocop:enable Rails/InverseOf
after_create :notify_clients_after_change after_create :notify_clients_after_change

View file

@ -104,7 +104,7 @@ returns
# - stip out * we already search for *query* - # - stip out * we already search for *query* -
query.delete! '*' query.delete! '*'
organizations = Organization.where_or_cis(%i[name note], "%#{query}%") organizations = Organization.where_or_cis(%i[name note], "%#{query}%")
.order(order_sql) .order(Arel.sql(order_sql))
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
.to_a .to_a
@ -118,7 +118,7 @@ returns
organizations_by_user = Organization.select("DISTINCT(organizations.id), #{order_select_sql}") organizations_by_user = Organization.select("DISTINCT(organizations.id), #{order_select_sql}")
.joins('LEFT OUTER JOIN users ON users.organization_id = organizations.id') .joins('LEFT OUTER JOIN users ON users.organization_id = organizations.id')
.where(User.or_cis(%i[firstname lastname email], "%#{query}%")) .where(User.or_cis(%i[firstname lastname email], "%#{query}%"))
.order(order_sql) .order(Arel.sql(order_sql))
.limit(limit) .limit(limit)
organizations_by_user.each do |organization_by_user| organizations_by_user.each do |organization_by_user|

View file

@ -4,8 +4,8 @@ class RecentView < ApplicationModel
include RecentView::Assets include RecentView::Assets
# rubocop:disable Rails/InverseOf # rubocop:disable Rails/InverseOf
belongs_to :ticket, foreign_key: 'o_id' belongs_to :ticket, foreign_key: 'o_id', optional: true
belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'recent_view_object_id' belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'recent_view_object_id', optional: true
# rubocop:enable Rails/InverseOf # rubocop:enable Rails/InverseOf
after_create :notify_clients after_create :notify_clients
@ -42,7 +42,7 @@ class RecentView < ApplicationModel
'MAX(id) as id') 'MAX(id) as id')
.group(:o_id, :recent_view_object_id, :created_by_id) .group(:o_id, :recent_view_object_id, :created_by_id)
.where(created_by_id: user.id) .where(created_by_id: user.id)
.order('MAX(created_at) DESC, MAX(id) DESC') .order(Arel.sql('MAX(created_at) DESC, MAX(id) DESC'))
.limit(limit) .limit(limit)
if object_name.present? if object_name.present?

View file

@ -9,5 +9,5 @@ class Sla < ApplicationModel
store :condition store :condition
store :data store :data
validates :name, presence: true validates :name, presence: true
belongs_to :calendar belongs_to :calendar, optional: true
end end

View file

@ -5,8 +5,8 @@ class StatsStore < ApplicationModel
include StatsStore::SearchIndex include StatsStore::SearchIndex
# rubocop:disable Rails/InverseOf # rubocop:disable Rails/InverseOf
belongs_to :stats_store_object, class_name: 'ObjectLookup', foreign_key: 'stats_store_object_id' belongs_to :stats_store_object, class_name: 'ObjectLookup', foreign_key: 'stats_store_object_id', optional: true
belongs_to :related_stats_store_object, class_name: 'ObjectLookup', foreign_key: 'related_stats_store_object_id' belongs_to :related_stats_store_object, class_name: 'ObjectLookup', foreign_key: 'related_stats_store_object_id', optional: true
# rubocop:enable Rails/InverseOf # rubocop:enable Rails/InverseOf
store :data store :data

View file

@ -5,8 +5,8 @@ require_dependency 'store/file'
class Store < ApplicationModel class Store < ApplicationModel
belongs_to :store_object, class_name: 'Store::Object' belongs_to :store_object, class_name: 'Store::Object', optional: true
belongs_to :store_file, class_name: 'Store::File' belongs_to :store_file, class_name: 'Store::File', optional: true
validates :filename, presence: true validates :filename, presence: true

View file

@ -2,8 +2,8 @@
class Tag < ApplicationModel class Tag < ApplicationModel
belongs_to :tag_object, class_name: 'Tag::Object' belongs_to :tag_object, class_name: 'Tag::Object', optional: true
belongs_to :tag_item, class_name: 'Tag::Item' belongs_to :tag_item, class_name: 'Tag::Item', optional: true
# the noop is needed since Layout/EmptyLines detects # the noop is needed since Layout/EmptyLines detects
# the block commend below wrongly as the measurement of # the block commend below wrongly as the measurement of

View file

@ -60,18 +60,18 @@ class Ticket < ApplicationModel
sanitized_html :note sanitized_html :note
belongs_to :group belongs_to :group, optional: true
belongs_to :organization belongs_to :organization, optional: true
has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy, inverse_of: :ticket has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy, inverse_of: :ticket
has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy, inverse_of: :ticket has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy, inverse_of: :ticket
belongs_to :state, class_name: 'Ticket::State' belongs_to :state, class_name: 'Ticket::State', optional: true
belongs_to :priority, class_name: 'Ticket::Priority' belongs_to :priority, class_name: 'Ticket::Priority', optional: true
belongs_to :owner, class_name: 'User' belongs_to :owner, class_name: 'User', optional: true
belongs_to :customer, class_name: 'User' belongs_to :customer, class_name: 'User', optional: true
belongs_to :created_by, class_name: 'User' belongs_to :created_by, class_name: 'User', optional: true
belongs_to :updated_by, class_name: 'User' belongs_to :updated_by, class_name: 'User', optional: true
belongs_to :create_article_type, class_name: 'Ticket::Article::Type' belongs_to :create_article_type, class_name: 'Ticket::Article::Type', optional: true
belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender' belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender', optional: true
self.inheritance_column = nil self.inheritance_column = nil
@ -1001,7 +1001,7 @@ perform active triggers on ticket
end end
triggers = if Rails.configuration.db_case_sensitive triggers = if Rails.configuration.db_case_sensitive
::Trigger.where(active: true).order('LOWER(name)') ::Trigger.where(active: true).order(Arel.sql('LOWER(name)'))
else else
::Trigger.where(active: true).order(:name) ::Trigger.where(active: true).order(:name)
end end

View file

@ -11,13 +11,13 @@ class Ticket::Article < ApplicationModel
include Ticket::Article::ChecksAccess include Ticket::Article::ChecksAccess
include Ticket::Article::Assets include Ticket::Article::Assets
belongs_to :ticket belongs_to :ticket, optional: true
has_one :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', foreign_key: :ticket_article_id, dependent: :destroy, inverse_of: :ticket_article has_one :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', foreign_key: :ticket_article_id, dependent: :destroy, inverse_of: :ticket_article
belongs_to :type, class_name: 'Ticket::Article::Type' belongs_to :type, class_name: 'Ticket::Article::Type', optional: true
belongs_to :sender, class_name: 'Ticket::Article::Sender' belongs_to :sender, class_name: 'Ticket::Article::Sender', optional: true
belongs_to :created_by, class_name: 'User' belongs_to :created_by, class_name: 'User', optional: true
belongs_to :updated_by, class_name: 'User' belongs_to :updated_by, class_name: 'User', optional: true
belongs_to :origin_by, class_name: 'User' belongs_to :origin_by, class_name: 'User', optional: true
before_create :check_subject, :check_body, :check_message_id_md5 before_create :check_subject, :check_body, :check_message_id_md5
before_update :check_subject, :check_body, :check_message_id_md5 before_update :check_subject, :check_body, :check_message_id_md5

View file

@ -121,9 +121,9 @@ returns
.where(access_condition) .where(access_condition)
.where(query_condition, *bind_condition) .where(query_condition, *bind_condition)
.joins(tables) .joins(tables)
.order("#{order_by} #{direction}") .order(Arel.sql("#{order_by} #{direction}"))
.limit(2000) .limit(2000)
.pluck(:id, :updated_at, order_by) .pluck(:id, :updated_at, Arel.sql(order_by))
tickets = ticket_result.map do |ticket| tickets = ticket_result.map do |ticket|
{ {

View file

@ -190,7 +190,7 @@ returns
.where(access_condition) .where(access_condition)
.where('(tickets.title LIKE ? OR tickets.number LIKE ? OR ticket_articles.body LIKE ? OR ticket_articles.from LIKE ? OR ticket_articles.to LIKE ? OR ticket_articles.subject LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ) .where('(tickets.title LIKE ? OR tickets.number LIKE ? OR ticket_articles.body LIKE ? OR ticket_articles.from LIKE ? OR ticket_articles.to LIKE ? OR ticket_articles.subject LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" )
.joins(:articles) .joins(:articles)
.order(order_sql) .order(Arel.sql(order_sql))
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
else else
@ -199,7 +199,7 @@ returns
.joins(tables) .joins(tables)
.where(access_condition) .where(access_condition)
.where(query_condition, *bind_condition) .where(query_condition, *bind_condition)
.order(order_sql) .order(Arel.sql(order_sql))
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
end end

View file

@ -3,8 +3,8 @@ class Ticket::State < ApplicationModel
include CanBeImported include CanBeImported
include ChecksLatestChangeObserved include ChecksLatestChangeObserved
belongs_to :state_type, class_name: 'Ticket::StateType', inverse_of: :states belongs_to :state_type, class_name: 'Ticket::StateType', inverse_of: :states, optional: true
belongs_to :next_state, class_name: 'Ticket::State' belongs_to :next_state, class_name: 'Ticket::State', optional: true
after_create :ensure_defaults after_create :ensure_defaults
after_update :ensure_defaults after_update :ensure_defaults

View file

@ -1,8 +1,8 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class Ticket::TimeAccounting < ApplicationModel class Ticket::TimeAccounting < ApplicationModel
belongs_to :ticket belongs_to :ticket, optional: true
belongs_to :ticket_article, class_name: 'Ticket::Article', inverse_of: :ticket_time_accounting belongs_to :ticket_article, class_name: 'Ticket::Article', inverse_of: :ticket_time_accounting, optional: true
after_create :ticket_time_unit_update after_create :ticket_time_unit_update
after_update :ticket_time_unit_update after_update :ticket_time_unit_update

View file

@ -2,7 +2,7 @@
class Token < ActiveRecord::Base class Token < ActiveRecord::Base
before_create :generate_token before_create :generate_token
belongs_to :user belongs_to :user, optional: true
store :preferences store :preferences
=begin =begin

View file

@ -1,4 +1,6 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
require_dependency 'karma/user'
class User < ApplicationModel class User < ApplicationModel
include CanBeImported include CanBeImported
include HasActivityStreamLog include HasActivityStreamLog
@ -20,7 +22,7 @@ class User < ApplicationModel
has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization' has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization'
has_many :tokens, after_add: :cache_update, after_remove: :cache_update has_many :tokens, after_add: :cache_update, after_remove: :cache_update
has_many :authorizations, after_add: :cache_update, after_remove: :cache_update has_many :authorizations, after_add: :cache_update, after_remove: :cache_update
belongs_to :organization, inverse_of: :members belongs_to :organization, inverse_of: :members, optional: true
before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier
before_validation :check_mail_delivery_failed, on: :update before_validation :check_mail_delivery_failed, on: :update

View file

@ -137,11 +137,17 @@ returns
users = if params[:role_ids] users = if params[:role_ids]
User.joins(:roles).where('roles.id' => params[:role_ids]).where( User.joins(:roles).where('roles.id' => params[:role_ids]).where(
'(users.firstname LIKE ? OR users.lastname LIKE ? OR users.email LIKE ? OR users.login LIKE ?) AND users.id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" '(users.firstname LIKE ? OR users.lastname LIKE ? OR users.email LIKE ? OR users.login LIKE ?) AND users.id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%"
).order(order_sql).offset(offset).limit(limit) )
.order(Arel.sql(order_sql))
.offset(offset)
.limit(limit)
else else
User.where( User.where(
'(firstname LIKE ? OR lastname LIKE ? OR email LIKE ? OR login LIKE ?) AND id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" '(firstname LIKE ? OR lastname LIKE ? OR email LIKE ? OR login LIKE ?) AND id != 1', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%"
).order(order_sql).offset(offset).limit(limit) )
.order(Arel.sql(order_sql))
.offset(offset)
.limit(limit)
end end
users users
end end

View file

@ -4,6 +4,6 @@ begin
rescue LoadError => e rescue LoadError => e
raise unless e.message.include?('spring') raise unless e.message.include?('spring')
end end
APP_PATH = File.expand_path('../../config/application', __FILE__) APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot' require_relative '../config/boot'
require 'rails/commands' require 'rails/commands'

View file

@ -1,29 +1,36 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'pathname' require 'fileutils'
include FileUtils # rubocop:disable Style/MixinUsage
# path to your application root. # path to your application root.
APP_ROOT = Pathname.new File.expand_path('..', __dir__) APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir APP_ROOT do def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
chdir APP_ROOT do
# This script is a starting point to setup your application. # This script is a starting point to setup your application.
# Add necessary setup steps to this file: # Add necessary setup steps to this file.
puts '== Installing dependencies ==' puts '== Installing dependencies =='
system 'gem install bundler --conservative' system! 'gem install bundler --conservative'
system 'bundle check || bundle install --jobs 8' system('bundle check') || system!('bundle install')
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
# puts "\n== Copying sample files ==" # puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml") # unless File.exist?('config/database.yml')
# system "cp config/database.yml.sample config/database.yml" # cp 'config/database.yml.sample', 'config/database.yml'
# end # end
puts "\n== Preparing database ==" puts "\n== Preparing database =="
system 'bin/rake db:setup' system! 'bin/rails db:setup'
puts "\n== Removing old logs and tempfiles ==" puts "\n== Removing old logs and tempfiles =="
system 'rm -f log/*' system! 'bin/rails log:clear tmp:clear'
system 'rm -rf tmp/cache'
puts "\n== Restarting application server ==" puts "\n== Restarting application server =="
system 'touch tmp/restart.txt' system! 'bin/rails restart'
end end

31
bin/update Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env ruby
require 'fileutils'
include FileUtils # rubocop:disable Style/MixinUsage
# path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
chdir APP_ROOT do
# This script is a way to update your development environment automatically.
# Add necessary update steps to this file.
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
puts "\n== Updating database =="
system! 'bin/rails db:migrate'
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
system! 'bin/rails restart'
end

9
bin/yarn Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env ruby
APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do
exec 'yarnpkg', *ARGV
rescue Errno::ENOENT
warn 'Yarn executable was not detected in the system.'
warn 'Download Yarn at https://yarnpkg.com/en/docs/install'
exit 1
end

View file

@ -1,4 +1,4 @@
require File.expand_path('boot', __dir__) require_relative 'boot'
require 'rails/all' require 'rails/all'
@ -11,6 +11,9 @@ Bundler.require(*Rails.groups)
module Zammad module Zammad
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded. # -- all .rb files in that directory are automatically loaded.

View file

@ -1,3 +1,4 @@
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile. require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.

View file

@ -1,5 +1,5 @@
# Load the Rails application. # Load the Rails application.
require File.expand_path('application', __dir__) require_relative 'application'
# Initialize the Rails application. # Initialize the Rails application.
Rails.application.initialize! Rails.application.initialize!

View file

@ -9,28 +9,68 @@ Rails.application.configure do
# Do not eager load code on boot. # Do not eager load code on boot.
config.eager_load = false config.eager_load = false
# Show full error reports and disable caching. # Show full error reports.
config.consider_all_requests_local = true config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.to_i}"
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# Store uploaded files on the local file system (see config/storage.yml for options)
# config.active_storage.service = :local
# Don't care if the mailer can't send. # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
# Print deprecation notices to the Rails logger. # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log config.active_support.deprecation = :log
# Raise an error on page load if there are pending migrations. # Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load config.active_record.migration_error = :page_load
# Do not compress assets # Highlight code that triggered database queries in logs.
config.assets.compress = false config.active_record.verbose_query_logs = true
# Debug mode disables concatenation and preprocessing of assets. # Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large # This option may cause significant delays in view rendering with a large
# number of complex assets. # number of complex assets.
#config.assets.debug = true
config.assets.debug = false config.assets.debug = false
# Suppress logger output for asset requests.
config.assets.quiet = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = false
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
config.assets.raise_runtime_errors = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# Automatically inject JavaScript needed for LiveReload # Automatically inject JavaScript needed for LiveReload
if ENV['RAKE_LIVE_RELOAD'].present? if ENV['RAKE_LIVE_RELOAD'].present?
require 'rack-livereload' require 'rack-livereload'
@ -43,16 +83,4 @@ Rails.application.configure do
live_reload_port: 35_738 live_reload_port: 35_738
) )
end end
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = false
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
config.assets.raise_runtime_errors = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
end end

View file

@ -14,11 +14,9 @@ Rails.application.configure do
config.consider_all_requests_local = false config.consider_all_requests_local = false
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
# Enable Rack::Cache to put a simple HTTP cache in front of your application # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# Add `rack-cache` to your Gemfile before enabling this. # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# For large-scale production use, consider using a caching reverse proxy like # config.require_master_key = true
# NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable serving static files from the `/public` folder by default since # Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this. # Apache or NGINX already handles this.
@ -31,16 +29,23 @@ Rails.application.configure do
# Do not fallback to assets pipeline if a precompiled asset is missed. # Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false config.assets.compile = false
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# Specifies the header that your server uses for sending files. # Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options)
# config.active_storage.service = :local
# Mount Action Cable outside main process or domain
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true # config.force_ssl = true
@ -49,19 +54,16 @@ Rails.application.configure do
config.log_level = :info config.log_level = :info
# Prepend all log lines with the following tags. # Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ] config.log_tags = [ :request_id ]
# Use a different logger for distributed setups. # Use a different cache store in production.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # config.cache_store = :mem_cache_store
if ENV['RAILS_LOG_TO_STDOUT'].present? # Use a real queuing backend for Active Job (and separate queues per environment)
logger = ActiveSupport::Logger.new(STDOUT) # config.active_job.queue_adapter = :resque
logger.formatter = config.log_formatter # config.active_job.queue_name_prefix = "zammad_#{Rails.env}"
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Enable serving of images, stylesheets, and JavaScripts from an asset server. config.action_mailer.perform_caching = false
# config.action_controller.asset_host = 'http://assets.example.com'
# Ignore bad email addresses and do not raise email delivery errors. # Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
@ -77,6 +79,19 @@ Rails.application.configure do
# Send deprecation notices to registered listeners. # Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
# config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV['RAILS_LOG_TO_STDOUT'].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations. # Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false config.active_record.dump_schema_after_migration = false

View file

@ -12,9 +12,11 @@ Rails.application.configure do
# preloads Rails for running tests, you may have to set it to true. # preloads Rails for running tests, you may have to set it to true.
config.eager_load = false config.eager_load = false
# Configure static file server for tests with Cache-Control for performance. # Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true config.public_file_server.enabled = true
config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
}
# Show full error reports and disable caching. # Show full error reports and disable caching.
config.consider_all_requests_local = true config.consider_all_requests_local = true
@ -26,6 +28,11 @@ Rails.application.configure do
# Disable request forgery protection in test environment. # Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory
# config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world. # Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the # The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array. # ActionMailer::Base.deliveries array.
@ -43,6 +50,7 @@ Rails.application.configure do
# Enable autoload # Enable autoload
config.dependency_loading = true config.dependency_loading = true
# Reduce log level to speed up test runs.
config.after_initialize do config.after_initialize do
ActiveRecord::Base.logger = Rails.logger.clone ActiveRecord::Base.logger = Rails.logger.clone
ActiveRecord::Base.logger.level = Logger::INFO ActiveRecord::Base.logger.level = Logger::INFO

View file

@ -0,0 +1,8 @@
# Be sure to restart your server when you modify this file.
# ActiveSupport::Reloader.to_prepare do
# ApplicationController.renderer.defaults.merge!(
# http_host: 'example.org',
# https: false
# )
# end

View file

@ -3,11 +3,15 @@
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0' Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path # Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path # Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
# Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets. # Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
Rails.application.config.assets.precompile += %w[application-print.css] Rails.application.config.assets.precompile += %w[application-print.css]
Rails.application.config.assets.precompile += %w[print.css] Rails.application.config.assets.precompile += %w[print.css]
Rails.application.config.assets.precompile += %w[knowledge_base.css knowledge_base_public.js knowledge_base_public_polyfills.js] Rails.application.config.assets.precompile += %w[knowledge_base.css knowledge_base_public.js knowledge_base_public_polyfills.js]

View file

@ -0,0 +1,25 @@
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
# If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
# Report CSP violations to a specified URI
# For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true

View file

@ -1,3 +1,5 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Specify a serializer for the signed and encrypted cookie jars.
# Valid options are :json, :marshal, and :hybrid.
Rails.application.config.action_dispatch.cookies_serializer = :marshal Rails.application.config.action_dispatch.cookies_serializer = :marshal

View file

@ -1,6 +1,12 @@
# set database preferences case ActiveRecord::Base.connection_config[:adapter]
when 'mysql2'
# defaults Rails.application.config.db_4bytes_utf8 = false
Rails.application.config.db_case_sensitive = false Rails.application.config.db_case_sensitive = false
Rails.application.config.db_like = 'LIKE' Rails.application.config.db_like = 'LIKE'
Rails.application.config.db_4bytes_utf8 = true Rails.application.config.db_null_byte = true
when 'postgresql'
Rails.application.config.db_4bytes_utf8 = true
Rails.application.config.db_case_sensitive = true
Rails.application.config.db_like = 'ILIKE'
Rails.application.config.db_null_byte = false
end

View file

@ -1,78 +0,0 @@
return if ActiveRecord::Base.connection_config[:adapter] != 'mysql2'
Rails.application.config.db_4bytes_utf8 = false
Rails.application.config.db_null_byte = true
connection = ActiveRecord::Base.connection
# rubocop:disable Rails/Output
# rubocop:disable Rails/Exit
# rubocop:disable Layout/IndentHeredoc
# Version check --------------------------------------------------------------
# mysql example: "5.7.3"
# mariadb example: "10.1.17-MariaDB"
server_version = connection.execute('SELECT @@version;').first.first
raise 'Unable to retrive database version' if server_version.blank?
version_number = Gem::Version.new(server_version.split('-').first)
vendor = server_version.split('-').second || 'MySQL'
case vendor
when 'MySQL'
if version_number < Gem::Version.new('5.6')
printf "\e[31m" # ANSI red
puts <<~MSG
Error: Incompatible database backend version
(MySQL 5.6+ required; #{version_number} found)
MSG
printf "\e[0m" # ANSI normal
exit 1
end
when 'MariaDB'
if version_number < Gem::Version.new('10.0')
printf "\e[31m" # ANSI red
puts <<~MSG
Error: Incompatible database backend version
(MariaDB 10.0+ required; #{version_number} found)
MSG
printf "\e[0m" # ANSI normal
exit 1
end
end
# Configuration check --------------------------------------------------------
# Before MySQL 8.0, the default value of max_allowed_packet was 1MB - 4MB,
# which is impractically small and can lead to failures processing emails.
#
# See https://github.com/zammad/zammad/issues/1759
# https://github.com/zammad/zammad/issues/1970
# https://github.com/zammad/zammad/issues/2034
max_allowed_packet = connection.execute('SELECT @@max_allowed_packet;').first.first
max_allowed_packet_mb = max_allowed_packet / 1024 / 1024
if max_allowed_packet_mb <= 4
printf "\e[31m" # ANSI red
puts <<~MSG
Error: Database config value 'max_allowed_packet' too small (#{max_allowed_packet_mb}MB)
Please increase this value in your #{vendor} configuration (64MB+ recommended).
MSG
printf "\e[0m" # ANSI normal
exit 1
end
if connection.execute("SHOW tables LIKE 'settings';").any? &&
Setting.get('postmaster_max_size').present? &&
Setting.get('postmaster_max_size').to_i > max_allowed_packet_mb
printf "\e[33m" # ANSI yellow
puts <<~MSG
Warning: Database config value 'max_allowed_packet' less than Zammad setting 'Maximum Email Size'
Zammad will fail to process emails (both incoming and outgoing)
larger than the value of 'max_allowed_packet' (#{max_allowed_packet_mb}MB).
Please increase this value in your #{vendor} configuration accordingly.
MSG
printf "\e[0m" # ANSI normal
end
# rubocop:enable Rails/Exit
# rubocop:enable Rails/Output
# rubocop:enable Layout/IndentHeredoc

View file

@ -1,29 +0,0 @@
return if ActiveRecord::Base.connection_config[:adapter] != 'postgresql'
Rails.application.config.db_case_sensitive = true
Rails.application.config.db_like = 'ILIKE'
Rails.application.config.db_null_byte = false
# rubocop:disable Rails/Output
# rubocop:disable Rails/Exit
# rubocop:disable Layout/IndentHeredoc
# Version check --------------------------------------------------------------
# example output: "9.5.0"
# "10.3 (Debian 10.3-2)"
server_version = ActiveRecord::Base.connection.execute('SHOW server_version;').first['server_version']
version_number = Gem::Version.new(server_version.split.first)
if version_number < Gem::Version.new('9.1')
printf "\e[31m" # ANSI red
puts <<~MSG
Error: Incompatible database backend version
(PostgreSQL 9.1+ required; #{version_number} found)
MSG
printf "\e[0m" # ANSI normal
exit 1
end
# rubocop:enable Rails/Exit
# rubocop:enable Rails/Output
# rubocop:enable Layout/IndentHeredoc

View file

@ -0,0 +1,4 @@
# Rails' constant auto-loading resolves to 'rails/initializable' instead
require_dependency 'zammad/application/initializer/db_preflight_check'
Zammad::Application::Initializer::DBPreflightCheck.perform

View file

@ -1,6 +1,7 @@
return if !ActiveRecord::Base.connected?
# sync logo to fs / only if settings already exists # sync logo to fs / only if settings already exists
if ActiveRecord::Base.connection.tables.include?('settings') return if ActiveRecord::Base.connection.tables.exclude?('settings')
if Setting.column_names.include?('state_current') return if Setting.column_names.exclude?('state_current')
StaticAssets.sync
end StaticAssets.sync
end

View file

@ -1,11 +1,9 @@
# update settings for searchable models # update settings for searchable models
if ActiveRecord::Base.connection.tables.include?('settings')
if Setting.columns_hash.key?('state_current') # TODO: remove me later begin
models_current = Models.searchable.map(&:to_s) return if !Setting.exists?(name: 'models_searchable')
models_config = Setting.get('models_searchable')
setting = Setting.find_by(name: 'models_searchable') Setting.set('models_searchable', Models.searchable.map(&:to_s))
if setting && models_current != models_config rescue ActiveRecord::StatementInvalid
Setting.set('models_searchable', models_current) nil
end
end
end end

View file

@ -10,5 +10,5 @@ end
# To enable root element in JSON for ActiveRecord objects. # To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do # ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true # self.include_root_in_json = true
# end # end

View file

@ -1,5 +1,33 @@
# Sample localization file for English. Add more files in this directory for other locales. # Files in the config/locales directory are used for internationalization
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. # and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
# <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# The following keys must be escaped otherwise they will not be retrieved by
# the default I18n backend:
#
# true, false, on, off, yes, no
#
# Instead, surround them with single quotes.
#
# en:
# 'true': 'foo'
#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.
en: en:
hello: "Hello world" hello: "Hello world"

View file

@ -1,3 +1,10 @@
%w[
.ruby-version
.rbenv-vars
tmp/restart.txt
tmp/caching-dev.txt
].each { |path| Spring.watch(path) }
module Spring module Spring
module Commands module Commands
class SchedulerRb class SchedulerRb

View file

@ -0,0 +1,19 @@
require 'zammad/application/initializer/db_preflight_check/base'
require 'zammad/application/initializer/db_preflight_check/mysql2'
require 'zammad/application/initializer/db_preflight_check/postgresql'
module Zammad
class Application
class Initializer
module DBPreflightCheck
def self.perform
adapter.perform
end
def self.adapter
@adapter ||= const_get(ActiveRecord::Base.connection_config[:adapter].capitalize)
end
end
end
end
end

View file

@ -0,0 +1,43 @@
# NOTE: Why use Mysql2::Client / PG::Connection over ActiveRecord::Base.connection?
#
# As of Rails 5.2, db:create now runs initializers prior to creating the DB.
# That means if an initializer tries to establish an ActiveRecord::Base.connection,
# it will raise an ActiveRecord::NoDatabaseError
# (see https://github.com/rails/rails/issues/32870 for more details).
#
# The workaround is to use the bare RDBMS library
# and connect without specifying a database (MySQL),
# or connect to a standard system database instead (PostgreSQL).
module Zammad
class Application
class Initializer
module DBPreflightCheck
module Base
def check_version_compatibility
return if Gem::Version.new(current_version) >= Gem::Version.new(min_version)
err(<<~MSG)
Incompatible database backend version
(#{vendor} #{min_version}+ required; #{current_version} found)
MSG
end
def warn(msg)
printf "\e[33m" # ANSI yellow
puts "Warning: #{msg}" # rubocop:disable Rails/Output
printf "\e[0m" # ANSI normal
end
def err(msg)
printf "\e[31m" # ANSI red
puts "Error: #{msg}" # rubocop:disable Rails/Output
printf "\e[0m" # ANSI normal
exit 1 # rubocop:disable Rails/Exit
end
end
end
end
end
end

View file

@ -0,0 +1,88 @@
# NOTE: Why use Mysql2::Client over ActiveRecord::Base.connection?
#
# As of Rails 5.2, db:create now runs initializers prior to creating the DB.
# That means if an initializer tries to establish an ActiveRecord::Base.connection,
# it will raise an ActiveRecord::NoDatabaseError
# (see https://github.com/rails/rails/issues/32870 for more details).
#
# The workaround is to use the bare RDBMS library
# and connect without specifying a database.
module Zammad
class Application
class Initializer
module DBPreflightCheck
module Mysql2
extend Base
def self.perform
check_version_compatibility
check_max_allowed_packet
end
# Configuration check --------------------------------------------------
# Before MySQL 8.0, the default value of max_allowed_packet was 1MB - 4MB,
# which is impractically small and can lead to email processing failures.
#
# See https://github.com/zammad/zammad/issues/1759
# https://github.com/zammad/zammad/issues/1970
# https://github.com/zammad/zammad/issues/2034
def self.check_max_allowed_packet
if max_allowed_packet_mb <= 4
err(<<~MSG)
Database config value 'max_allowed_packet' too small (#{max_allowed_packet_mb}MB)
Please increase this value in your #{vendor} configuration (64MB+ recommended).
MSG
elsif max_allowed_packet_mb < Setting.get('postmaster_max_size').to_i
warn(<<~MSG)
Database config value 'max_allowed_packet' less than Zammad setting 'Maximum Email Size'
Zammad will fail to process emails (both incoming and outgoing)
larger than the value of 'max_allowed_packet' (#{max_allowed_packet_mb}MB).
Please increase this value in your #{vendor} configuration accordingly.
MSG
end
# Setting.get will fail if 'settings' table does not exist
rescue ActiveRecord::StatementInvalid # rubocop:disable Lint/HandleExceptions
end
def self.connection
@connection ||= ::Mysql2::Client.new(db_config)
end
def self.db_config
@db_config ||= ActiveRecord::Base.connection_config
.symbolize_keys
.except(:database)
end
# formats: "5.7.3" (MySQL)
# "10.1.17-MariaDB" (MariaDB)
def self.current_version
@current_version ||= mysql_variable('version').split('-').first
end
def self.min_version
case vendor
when 'MySQL'
'5.6'
when 'MariaDB'
'10.0'
end
end
def self.vendor
@vendor ||= mysql_variable('version').split('-').second || 'MySQL'
end
def self.max_allowed_packet_mb
@max_allowed_packet_mb ||= mysql_variable('max_allowed_packet') / 1024 / 1024
end
def self.mysql_variable(name)
connection.query("SELECT @@#{name};").first["@@#{name}"]
end
end
end
end
end
end

View file

@ -0,0 +1,73 @@
# NOTE: Why use PG::Connection over ActiveRecord::Base.connection?
#
# As of Rails 5.2, db:create now runs initializers prior to creating the DB.
# That means if an initializer tries to establish an ActiveRecord::Base.connection,
# it will raise an ActiveRecord::NoDatabaseError
# (see https://github.com/rails/rails/issues/32870 for more details).
#
# The workaround is to use the bare RDBMS library
# and connect to a standard system database instead.
module Zammad
class Application
class Initializer
module DBPreflightCheck
module Postgresql
extend Base
def self.perform
check_version_compatibility
ensure
connection.try(:finish)
end
def self.check_version_compatibility
return if connection.nil? # Edge case: if Postgres can't find a DB to connect to
super
end
def self.connection
alternate_dbs = %w[template0 template1 postgres]
@connection ||= begin
PG.connect(db_config)
rescue PG::ConnectionBad
db_config[:dbname] = alternate_dbs.pop
retry if db_config[:dbname].present?
end
end
# Adapted from ActiveRecord::ConnectionHandling#postgresql_connection
def self.db_config
@db_config ||= ActiveRecord::Base.connection_config.dup.tap do |config|
config.symbolize_keys!
config[:user] = config.delete(:username)
config[:dbname] = config.delete(:database)
config.slice!(*PG::Connection.conndefaults_hash.keys, :requiressl)
config.compact!
end
end
# formats: "9.5.0"
# "10.3 (Debian 10.3-2)"
def self.current_version
@current_version ||= pg_variable('server_version').split.first
end
def self.min_version
@min_version ||= '9.1'
end
def self.vendor
@vendor ||= 'PostgreSQL'
end
def self.pg_variable(name)
connection.exec("SHOW #{name};").first[name]
end
end
end
end
end
end

View file

@ -1886,7 +1886,7 @@ RSpec.describe String do
.to raise_error(Encoding::InvalidByteSequenceError) .to raise_error(Encoding::InvalidByteSequenceError)
expect { subject.utf8_encode(from: 'gb2312') } expect { subject.utf8_encode(from: 'gb2312') }
.not_to raise_error(Encoding::InvalidByteSequenceError) .not_to raise_error
end end
it 'uses the detected input encoding instead' do it 'uses the detected input encoding instead' do

View file

@ -113,8 +113,8 @@ RSpec.describe Cti::CallerId do
context 'shared by multiple CallerIds' do context 'shared by multiple CallerIds' do
context '(for different users)' do context '(for different users)' do
subject!(:caller_ids) do subject!(:caller_ids) do
[create(:caller_id, caller_id: number, user: User.last), [create(:caller_id, caller_id: number, user: create(:user)),
create(:caller_id, caller_id: number, user: User.offset(1).last)] create(:caller_id, caller_id: number, user: create(:user))]
end end
it 'returns all corresponding CallerId records' do it 'returns all corresponding CallerId records' do
@ -132,9 +132,8 @@ RSpec.describe Cti::CallerId do
context '(some for the same user, some for another)' do context '(some for the same user, some for another)' do
subject!(:caller_ids) do subject!(:caller_ids) do
[create(:caller_id, caller_id: number, user: User.last), [*create_list(:caller_id, 2, caller_id: number, user: create(:user)),
create(:caller_id, caller_id: number, user: User.last), create(:caller_id, caller_id: number, user: create(:user))]
create(:caller_id, caller_id: number, user: User.offset(1).last)]
end end
it 'returns one CallerId record per unique #user_id, by MAX(id)' do it 'returns one CallerId record per unique #user_id, by MAX(id)' do

View file

@ -14,7 +14,7 @@ RSpec.describe KnowledgeBase::Category, type: :model do
it { is_expected.to have_many(:answers) } it { is_expected.to have_many(:answers) }
it { is_expected.to have_many(:children) } it { is_expected.to have_many(:children) }
it { is_expected.to belong_to(:parent) } it { is_expected.to belong_to(:parent).optional }
it { is_expected.to belong_to(:knowledge_base) } it { is_expected.to belong_to(:knowledge_base) }
context 'in multilevel tree' do context 'in multilevel tree' do

View file

@ -49,6 +49,8 @@ class ChatTest < ActiveSupport::TestCase
# see: https://github.com/zammad/zammad/issues/2353 # see: https://github.com/zammad/zammad/issues/2353
test 'chat event db connection test' do test 'chat event db connection test' do
skip "Can't properly disconnect while Spring is in use." if defined?(Spring)
class DummyWs class DummyWs
def send(msg) def send(msg)
Rails.logger.info "WS send: #{msg}" Rails.logger.info "WS send: #{msg}"