1require 'bcrypt' |
|
2require 'uri' |
|
3require 'active_support/number_helper' |
|
|
5class Site < ActiveRecord::Base |
6 class ServiceUnavailable < StandardError; end |
|
8 include ActiveSupport::NumberHelper |
|
10 FALSE_VALUES = [nil, false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set |
|
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 |
|
|
311 def authenticate(username, password) |
312 self.username == username && self.password_digest == password |
|
313 end |
|
|
315 def email_protocol |
316 uri.scheme
|
|
317 end |
|
|
319 def formatted_threshold_for_moderation |
320 number_to_delimited(threshold_for_moderation)
|
|
321 end |
|
|
323 def formatted_threshold_for_response |
324 number_to_delimited(threshold_for_response)
|
|
325 end |
|
|
327 def formatted_threshold_for_debate |
328 number_to_delimited(threshold_for_debate)
|
|
329 end |
|
|
331 def host |
332 uri.host
|
|
333 end |
|
|
335 def host_with_port |
336 "#{host}#{port_string(uri)}" |
|
337 end |
|
|
339 def port |
340 uri.port
|
|
341 end |
|
|
343 def protocol |
344 "#{uri.scheme}://" |
|
345 end |
|
|
347 def constraints_for_public |
348 unless database_migrating? |
|
349 { protocol: protocol, host: host, port: port } |
|
350 end |
|
351 end |
|
|
353 def moderate_host |
354 moderate_uri.host
|
|
355 end |
|
|
357 def moderate_host_with_port |
358 "#{moderate_host}#{port_string(moderate_uri)}" |
|
359 end |
|
|
361 def moderate_port |
362 moderate_uri.port
|
|
363 end |
|
|
365 def moderate_protocol |
366 "#{moderate_uri.scheme}://" |
|
367 end |
|
|
369 def constraints_for_moderation |
370 unless database_migrating? |
|
371 { protocol: moderate_protocol, host: moderate_host, port: moderate_port } |
|
372 end |
|
373 end |
|
|
375 def password_digest |
376 BCrypt::Password.new(super) |
|
377 end |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
427 def update_all(updates) |
428 if scope.update_all(updates) > 0 |
|
429 reload
|
|
430 else |
|
431 false |
|
432 end |
|
433 end |
|
435 private
|
|
|
437 def scope |
438 self.class.unscoped.where(id: id) |
|
439 end |
|
|
441 def database_migrating? |
442 ARGV.any?{ |arg| arg =~ /db:migrate/ } |
|
443 end |
|
|
445 def port_string(uri) |
446 standard_port?(uri) ? '' : ":#{uri.port}" |
|
447 end |
|
|
449 def standard_port(uri) |
450 case uri.scheme |
|
451 when 'https' then 443 |
|
452 else 80 |
|
453 end |
|
454 end |
|
|
456 def standard_port?(uri) |
457 uri.port == standard_port(uri)
|
|
458 end |
|
|
460 def uri |
461 @uri ||= URI.parse(url) |
|
462 end |
|
|
464 def moderate_uri |
465 @moderate_uri ||= URI.parse(moderate_url) |
|
466 end |
|
|
468 def type_cast_feature_flag(value) |
469 value.in?(FALSE_VALUES) ? false : true |
|
470 end |
|
471end |