1require 'bcrypt'
 
2require 'uri'
 
3require 'active_support/number_helper'
 
4
  • Class "Site" has 463 lines. It should have 300 or less. » roodi
5class Site < ActiveRecord::Base
 
6  class ServiceUnavailable < StandardError; end
 
7
 
8  include ActiveSupport::NumberHelper
 
 9
 
10  FALSE_VALUES = [nil, false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
 
 
12  FEATURE_FLAGS = %w[
13
 
13    disable_constituency_api
 
14    disable_trending_petitions
 
15    disable_invalid_signature_count_check
 
16    disable_daily_update_statistics_job
 
17    disable_plus_address_check
 
18    disable_feedback_sending
 
19  ]
 
 
21  class << self
 
22    def table_exists?
 
23      @table_exists ||= connection.table_exists?(table_name)
 
24    end
 
 
26    def before_remove_const
 
27      Thread.current[:__site__] = nil
 
28    end
 
 
30    def instance
 
31      Thread.current[:__site__] ||= first_or_create(defaults)
 
32    end
 
 
34    def authenticate(username, password)
 
35      instance.authenticate(username, password)
 
36    end
 
 
38    def email_protocol
 
39      instance.email_protocol
 
40    end
 
 
42    def enabled?
 
43      instance.enabled?
 
44    end
 
 
46    def formatted_threshold_for_moderation
 
47      instance.formatted_threshold_for_moderation
 
48    end
 
 
50    def formatted_threshold_for_response
 
51      instance.formatted_threshold_for_response
 
52    end
 
 
54    def formatted_threshold_for_debate
 
55      instance.formatted_threshold_for_debate
 
56    end
 
 
58    def host
 
59      instance.host
 
60    end
 
 
62    def host_with_port
 
63      instance.host_with_port
 
64    end
 
 
66    def constraints_for_public
 
67      if table_exists?
 
68        instance.constraints_for_public
 
69      else
 
70        default_constraints_for_public
 
71      end
 
72    end
 
 
74    def moderate_host
 
75      instance.moderate_host
 
76    end
 
 
78    def moderate_host_with_port
 
79      instance.moderate_host_with_port
 
80    end
 
 
82    def constraints_for_moderation
 
83      if table_exists?
 
84        instance.constraints_for_moderation
 
85      else
 
86        default_constraints_for_moderation
 
87      end
 
88    end
 
 
90    def opened_at_for_closing(time = Time.current)
 
91      instance.opened_at_for_closing(time)
 
92    end
 
 
94    def closed_at_for_opening(time = Time.current)
 
95      instance.closed_at_for_opening(time)
 
96    end
 
 
98    def port
 
 99      instance.port
 
100    end
 
 
102    def protected?
 
103      instance.protected?
 
104    end
 
 
106    def login_timeout
 
107      instance.login_timeout
 
108    end
 
 
110    def reload
 
111      Thread.current[:__site__] = nil
 
112    end
 
 
114    def touch(*names)
 
115      instance.touch(*names)
 
116    end
 
 
118    def disable_signature_counts!
 
119      instance.update!(update_signature_counts: false)
 
120    end
 
 
122    def enable_signature_counts!
 
123      instance.update!(update_signature_counts: true)
 
124    end
 
 
126    def last_checked_at!(timestamp = Time.current)
 
127      instance.update_all(last_petition_created_at: timestamp)
 
128    end
 
 
130    def last_petition_created_at!(timestamp = Time.current)
 
131      instance.update_all(last_petition_created_at: timestamp)
 
132    end
 
 
134    def signature_count_updated_at!(timestamp = Time.current)
 
135      instance.update_all(signature_count_updated_at: timestamp)
 
136    end
 
 
138    def moderation_overdue_in_days
 
139      7.days
 
140    end
 
 
142    def moderation_near_overdue_in_days
 
143      5.days
 
144    end
 
 
146    def defaults
 
147      {
 
148        title:                          default_title,
 
149        url:                            default_url,
 
150        moderate_url:                   default_moderate_url,
 
151        email_from:                     default_email_from,
 
152        feedback_email:                 default_feedback_email,
 
153        username:                       default_username,
 
154        password:                       default_password,
 
155        enabled:                        default_enabled,
 
156        protected:                      default_protected,
 
157        login_timeout:                  default_login_timeout,
 
158        petition_duration:              default_petition_duration,
 
159        minimum_number_of_sponsors:     default_minimum_number_of_sponsors,
 
160        maximum_number_of_sponsors:     default_maximum_number_of_sponsors,
 
161        threshold_for_moderation:       default_threshold_for_moderation,
 
162        threshold_for_moderation_delay: default_threshold_for_moderation_delay,
 
163        threshold_for_response:         default_threshold_for_response,
 
164        threshold_for_debate:           default_threshold_for_debate
 
165      }
 
166    end
 
 
168    private
 
 
170    def default_title
 
171      ENV.fetch('SITE_TITLE', 'Petition parliament')
 
172    end
 
 
174    def default_scheme
 
175      ENV.fetch('EPETITIONS_PROTOCOL', 'https')
 
176    end
 
 
178    def default_protocol
 
179      "#{default_scheme}://"
 
180    end
 
 
182    def default_url
 
183      if ENV.fetch('EPETITIONS_PROTOCOL', 'https') == 'https'
 
184        URI::HTTPS.build(default_url_components).to_s
 
185      else
 
186        URI::HTTP.build(default_url_components).to_s
 
187      end
 
188    end
 
 
190    def default_url_components
 
191      [nil, default_host, default_port, nil, nil, nil]
 
192    end
 
 
194    def default_host
 
195      ENV.fetch('EPETITIONS_HOST', 'petition.parliament.uk')
 
196    end
 
 
198    def default_domain(tld_length = 1)
 
199      ActionDispatch::Http::URL.extract_domain(default_host, tld_length)
 
200    end
 
 
202    def default_moderate_url
 
203      if ENV.fetch('EPETITIONS_PROTOCOL', 'https') == 'https'
 
204        URI::HTTPS.build(default_moderate_url_components).to_s
 
205      else
 
206        URI::HTTP.build(default_moderate_url_components).to_s
 
207      end
 
208    end
 
 
210    def default_moderate_url_components
 
211      [nil, default_moderate_host, default_port, nil, nil, nil]
 
212    end
 
 
214    def default_moderate_host
 
215      ENV.fetch('MODERATE_HOST', 'moderate.petition.parliament.uk')
 
216    end
 
 
218    def default_port
 
219      ENV.fetch('EPETITIONS_PORT', '443').to_i
 
220    end
 
 
222    def default_email_from
 
223      ENV.fetch('EPETITIONS_FROM', %{"Petitions: UK Government and Parliament" <no-reply@#{default_host}>})
 
224    end
 
 
226    def default_feedback_email
 
227      ENV.fetch('EPETITIONS_FEEDBACK', %{"Petitions: UK Government and Parliament" <petitionscommittee@#{default_domain}>})
 
228    end
 
 
230    def default_username
 
231      ENV.fetch('SITE_USERNAME', nil).presence
 
232    end
 
 
234    def default_password
 
235      ENV.fetch('SITE_PASSWORD', nil).presence
 
236    end
 
 
238    def default_enabled
 
239      !ENV.fetch('SITE_ENABLED', '1').to_i.zero?
 
240    end
 
 
242    def default_protected
 
243      !ENV.fetch('SITE_PROTECTED', '0').to_i.zero?
 
244    end
 
 
246    def default_login_timeout
 
247      ENV.fetch('SITE_LOGIN_TIMEOUT', '1800').to_i
 
248    end
 
 
250    def default_petition_duration
 
251      ENV.fetch('PETITION_DURATION', '6').to_i
 
252    end
 
 
254    def default_minimum_number_of_sponsors
 
255      ENV.fetch('MINIMUM_NUMBER_OF_SPONSORS', '5').to_i
 
256    end
 
 
258    def default_maximum_number_of_sponsors
 
259      ENV.fetch('MAXIMUM_NUMBER_OF_SPONSORS', '20').to_i
 
260    end
 
 
262    def default_threshold_for_moderation
 
263      ENV.fetch('THRESHOLD_FOR_MODERATION', '5').to_i
 
264    end
 
 
266    def default_threshold_for_moderation_delay
 
267      ENV.fetch('THRESHOLD_FOR_MODERATION_DELAY', '500').to_i
 
268    end
 
 
270    def default_threshold_for_response
 
271      ENV.fetch('THRESHOLD_FOR_RESPONSE', '10000').to_i
 
272    end
 
 
274    def default_threshold_for_debate
 
275      ENV.fetch('THRESHOLD_FOR_DEBATE', '100000').to_i
 
276    end
 
 
278    def default_constraints_for_public
 
279      { protocol: default_protocol, host: default_host, port: default_port }
 
280    end
 
 
282    def default_constraints_for_moderation
 
283      { protocol: default_protocol, host: default_moderate_host, port: default_port }
 
284    end
 
285  end
 
 
287  if table_exists?
 
288    column_names.map(&:to_sym).each do |column|
 
289      define_singleton_method(column) do |*args, &block|
 
290        instance.public_send(column, *args, &block)
 
291      end
 
292    end
 
293  end
 
 
295  FEATURE_FLAGS.each do |feature_flag|
 
296    define_singleton_method(:"#{feature_flag}?") do |*args, &block|
 
297      instance.public_send(feature_flag, *args, &block)
 
298    end
 
 
300    define_method(:"#{feature_flag}=") do |value|
 
301      write_store_attribute(:feature_flags, feature_flag, type_cast_feature_flag(value))
 
302    end
 
 
304    define_method(feature_flag) do
 
305      read_store_attribute(:feature_flags, feature_flag)
 
306    end
 
307  end
 
 
309  attr_reader :password
 
  • ControlParameter - is controlled by argument 'username' » reek
  • Complexity 1 » saikuro
311  def authenticate(username, password)
 
312    self.username == username && self.password_digest == password
 
313  end
 
  • Complexity 1 » saikuro
315  def email_protocol
 
316    uri.scheme
 
317  end
 
  • Complexity 1 » saikuro
319  def formatted_threshold_for_moderation
 
320    number_to_delimited(threshold_for_moderation)
 
321  end
 
  • Complexity 1 » saikuro
323  def formatted_threshold_for_response
 
324    number_to_delimited(threshold_for_response)
 
325  end
 
  • Complexity 1 » saikuro
327  def formatted_threshold_for_debate
 
328    number_to_delimited(threshold_for_debate)
 
329  end
 
  • Complexity 1 » saikuro
331  def host
 
332    uri.host
 
333  end
 
  • Complexity 1 » saikuro
335  def host_with_port
 
336    "#{host}#{port_string(uri)}"
 
337  end
 
  • Complexity 1 » saikuro
339  def port
 
340    uri.port
 
341  end
 
  • Complexity 1 » saikuro
343  def protocol
 
344    "#{uri.scheme}://"
 
345  end
 
  • Complexity 2 » saikuro
347  def constraints_for_public
 
348    unless database_migrating?
 
349      { protocol: protocol, host: host, port: port }
 
350    end
 
351  end
 
  • Complexity 1 » saikuro
353  def moderate_host
 
354    moderate_uri.host
 
355  end
 
  • Complexity 1 » saikuro
357  def moderate_host_with_port
 
358    "#{moderate_host}#{port_string(moderate_uri)}"
 
359  end
 
  • Complexity 1 » saikuro
361  def moderate_port
 
362    moderate_uri.port
 
363  end
 
  • Complexity 1 » saikuro
365  def moderate_protocol
 
366    "#{moderate_uri.scheme}://"
 
367  end
 
  • Complexity 2 » saikuro
369  def constraints_for_moderation
 
370    unless database_migrating?
 
371      { protocol: moderate_protocol, host: moderate_host, port: moderate_port }
 
372    end
 
373  end
 
  • Complexity 1 » saikuro
375  def password_digest
 
376    BCrypt::Password.new(super)
 
377  end
 
  • Complexity 2 » saikuro
379  def password=(new_password)
 
380    @password = new_password.presence
 
 
382    if @password
 
383      self.password_digest = BCrypt::Password.create(@password, cost: 10)
 
384    end
 
385  end
 
  • FeatureEnvy - refers to 'opened_at' more than self (maybe move it to another class?) » reek
  • FeatureEnvy - refers to 'time' more than self (maybe move it to another class?) » reek
  • Complexity 2 » saikuro
387  def opened_at_for_closing(time = Time.current)
 
388    opened_at = time.beginning_of_day - petition_duration.months
 
 
390    if opened_at.day < time.day
 
391      opened_at + 1.day
 
392    else
 
393      opened_at
 
394    end
 
395  end
 
  • Complexity 1 » saikuro
397  def closed_at_for_opening(time = Time.current)
 
398    time.end_of_day + petition_duration.months
 
399  end
 
 
401  validates :title, presence: true, length: { maximum: 50 }
 
402  validates :url, presence: true, length: { maximum: 50 }
 
403  validates :moderate_url, presence: true, length: { maximum: 50 }
 
404  validates :email_from, presence: true, length: { maximum: 100 }
 
405  validates :feedback_email, presence: true, length: { maximum: 100 }
 
406  validates :petition_duration, presence: true, numericality: { only_integer: true }
 
407  validates :minimum_number_of_sponsors, presence: true, numericality: { only_integer: true }
 
408  validates :maximum_number_of_sponsors, presence: true, numericality: { only_integer: true }
 
409  validates :threshold_for_moderation, presence: true, numericality: { only_integer: true }
 
410  validates :threshold_for_moderation_delay, presence: true, numericality: { only_integer: true }
 
411  validates :threshold_for_response, presence: true, numericality: { only_integer: true }
 
412  validates :threshold_for_debate, presence: true, numericality: { only_integer: true }
 
413  validates :username, presence: true, length: { maximum: 30 }, if: :protected?
 
414  validates :password, length: { maximum: 30 }, confirmation: true, if: :protected?
 
415  validates :login_timeout, presence: true, numericality: { only_integer: true }
 
 
417  validate if: :protected? do
 
418    errors.add(:password, :blank) unless password_digest?
 
419  end
 
 
421  before_save if: :update_signature_counts_changed? do
 
422    if update_signature_counts
 
423      UpdateSignatureCountsJob.perform_later
 
424    end
 
425  end
 
  • Complexity 2 » saikuro
427  def update_all(updates)
 
428    if scope.update_all(updates) > 0
 
429      reload
 
430    else
 
431      false
 
432    end
 
433  end
 
 
435  private
 
  • Complexity 1 » saikuro
437  def scope
 
438    self.class.unscoped.where(id: id)
 
439  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 2 » saikuro
441  def database_migrating?
 
442    ARGV.any?{ |arg| arg =~ /db:migrate/ }
 
443  end
 
  • Complexity 2 » saikuro
445  def port_string(uri)
 
446    standard_port?(uri) ? '' : ":#{uri.port}"
 
447  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 2 » saikuro
449  def standard_port(uri)
 
450    case uri.scheme
 
451      when 'https' then 443
 
452      else 80
 
453    end
 
454  end
 
  • Complexity 1 » saikuro
456  def standard_port?(uri)
 
457    uri.port == standard_port(uri)
 
458  end
 
  • Complexity 1 » saikuro
460  def uri
 
461    @uri ||= URI.parse(url)
 
462  end
 
  • Complexity 1 » saikuro
464  def moderate_uri
 
465    @moderate_uri ||= URI.parse(moderate_url)
 
466  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 2 » saikuro
468  def type_cast_feature_flag(value)
 
469    value.in?(FALSE_VALUES) ? false : true
 
470  end
 
471end