biovision-base 0.5.170614 → 0.7.170709

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -0
  3. data/app/assets/images/biovision/base/placeholders/image.svg +1 -1
  4. data/app/assets/images/biovision/base/placeholders/region_image.svg +1 -0
  5. data/app/assets/stylesheets/biovision/base/admin.scss +226 -31
  6. data/app/assets/stylesheets/biovision/base/biovision.scss +45 -104
  7. data/app/assets/stylesheets/biovision/base/buttons.scss +3 -2
  8. data/app/assets/stylesheets/biovision/base/default.scss +66 -45
  9. data/app/assets/stylesheets/biovision/base/default_admin.scss +10 -0
  10. data/app/assets/stylesheets/biovision/base/default_application.scss +8 -0
  11. data/app/assets/stylesheets/biovision/base/filters.scss +4 -4
  12. data/app/assets/stylesheets/biovision/base/layout.scss +113 -0
  13. data/app/assets/stylesheets/biovision/base/message-box.scss +3 -3
  14. data/app/assets/stylesheets/biovision/base/regions.scss +9 -0
  15. data/app/assets/stylesheets/biovision/base/tootik.scss +6 -6
  16. data/app/assets/stylesheets/biovision/base/track.scss +7 -6
  17. data/app/assets/stylesheets/biovision/base/users.scss +64 -0
  18. data/app/controllers/admin/login_attempts_controller.rb +6 -0
  19. data/app/controllers/admin/privileges_controller.rb +37 -1
  20. data/app/controllers/admin/regions_controller.rb +37 -0
  21. data/app/controllers/admin/users_controller.rb +2 -2
  22. data/app/controllers/authentication_controller.rb +27 -26
  23. data/app/controllers/concerns/authentication.rb +20 -0
  24. data/app/controllers/my/confirmations_controller.rb +8 -3
  25. data/app/controllers/my/login_attempts_controller.rb +9 -0
  26. data/app/controllers/my/profiles_controller.rb +10 -2
  27. data/app/controllers/my/tokens_controller.rb +20 -0
  28. data/app/controllers/regions_controller.rb +73 -0
  29. data/app/helpers/biovision_regions_helper.rb +22 -0
  30. data/app/mailers/application_mailer.rb +5 -0
  31. data/app/mailers/user_mailer.rb +8 -0
  32. data/app/models/central_region.rb +49 -0
  33. data/app/models/concerns/required_unique_name.rb +1 -1
  34. data/app/models/concerns/required_unique_slug.rb +1 -1
  35. data/app/models/login_attempt.rb +24 -0
  36. data/app/models/privilege.rb +176 -1
  37. data/app/models/region.rb +100 -0
  38. data/app/models/user.rb +118 -1
  39. data/app/models/user_privilege.rb +52 -1
  40. data/app/services/code_manager/confirmation.rb +1 -1
  41. data/app/services/user_bouncer.rb +37 -0
  42. data/app/uploaders/header_image_uploader.rb +50 -0
  43. data/app/uploaders/region_image_uploader.rb +53 -0
  44. data/app/views/admin/agents/entity/_preview.jbuilder +8 -0
  45. data/app/views/admin/codes/index.html.erb +1 -1
  46. data/app/views/admin/editable_pages/index.html.erb +1 -1
  47. data/app/views/admin/index/index.html.erb +4 -0
  48. data/app/views/admin/login_attempts/_nav_item.html.erb +6 -0
  49. data/app/views/admin/login_attempts/entity/_in_list.html.erb +16 -0
  50. data/app/views/admin/login_attempts/included/_agents.jbuilder +3 -0
  51. data/app/views/admin/login_attempts/included/_users.jbuilder +3 -0
  52. data/app/views/admin/login_attempts/index.html.erb +16 -0
  53. data/app/views/admin/login_attempts/index.jbuilder +28 -0
  54. data/app/views/admin/privilege_groups/index.html.erb +1 -1
  55. data/app/views/admin/privileges/_toggleable.html.erb +7 -0
  56. data/app/views/admin/privileges/entity/_in_list.html.erb +1 -0
  57. data/app/views/admin/privileges/entity/_region.html.erb +12 -0
  58. data/app/views/admin/privileges/regions.jbuilder +10 -0
  59. data/app/views/admin/privileges/show.html.erb +2 -0
  60. data/app/views/admin/regions/_nav_item.html.erb +2 -0
  61. data/app/views/admin/regions/_toggleable.html.erb +7 -0
  62. data/app/views/admin/regions/entity/_in_list.html.erb +32 -0
  63. data/app/views/admin/regions/index.html.erb +22 -0
  64. data/app/views/admin/regions/show.html.erb +95 -0
  65. data/app/views/admin/tokens/index.html.erb +1 -1
  66. data/app/views/admin/users/_search.html.erb +1 -1
  67. data/app/views/admin/users/entity/_preview.jbuilder +12 -0
  68. data/app/views/admin/users/entity/_privilege.html.erb +23 -7
  69. data/app/views/admin/users/entity/_privilege_tree.html.erb +2 -2
  70. data/app/views/admin/users/privileges.html.erb +47 -12
  71. data/app/views/admin/users/show.html.erb +17 -1
  72. data/app/views/admin/users/tokens.html.erb +1 -1
  73. data/app/views/authentication/new.html.erb +1 -2
  74. data/app/views/layouts/admin/_footer.html.erb +8 -0
  75. data/app/views/layouts/application/_footer.html.erb +5 -0
  76. data/app/views/layouts/application/_header.html.erb +9 -0
  77. data/app/views/layouts/application/header/_authentication.html.erb +7 -0
  78. data/app/views/layouts/application/header/_logo.html.erb +3 -0
  79. data/app/views/layouts/application/header/_navigation.html.erb +0 -0
  80. data/app/views/layouts/application/header/authentication/_links.html.erb +4 -0
  81. data/app/views/layouts/application/header/authentication/_plate.html.erb +4 -0
  82. data/app/views/layouts/mailer.html.erb +13 -0
  83. data/app/views/layouts/mailer.text.erb +1 -0
  84. data/app/views/my/confirmations/show.html.erb +11 -1
  85. data/app/views/my/index/index.html.erb +6 -4
  86. data/app/views/{admin/tokens → my/login_attempts}/_list.html.erb +1 -1
  87. data/app/views/my/login_attempts/_nav_item.html.erb +6 -0
  88. data/app/views/my/login_attempts/entity/_in_list.html.erb +13 -0
  89. data/app/views/my/login_attempts/included/_agents.jbuilder +7 -0
  90. data/app/views/my/login_attempts/index.html.erb +13 -0
  91. data/app/views/my/login_attempts/index.jbuilder +22 -0
  92. data/app/views/my/profiles/_nav_item.html.erb +6 -0
  93. data/app/views/my/profiles/new/_form.html.erb +49 -23
  94. data/app/views/my/profiles/new.html.erb +2 -2
  95. data/app/views/{admin/codes → my/tokens}/_list.html.erb +1 -1
  96. data/app/views/my/tokens/_nav_item.html.erb +6 -0
  97. data/app/views/my/tokens/_toggleable.html.erb +7 -0
  98. data/app/views/my/tokens/entity/_in_list.html.erb +18 -0
  99. data/app/views/my/tokens/index.html.erb +13 -0
  100. data/app/views/privileges/_form.html.erb +7 -0
  101. data/app/views/regions/_form.html.erb +73 -0
  102. data/app/views/regions/edit.html.erb +20 -0
  103. data/app/views/regions/new.html.erb +17 -0
  104. data/app/views/shared/_counters.html.erb +0 -0
  105. data/app/views/shared/_pagination.jbuilder +9 -0
  106. data/app/views/{admin/editable_pages → shared/admin}/_list.html.erb +2 -2
  107. data/app/views/user_mailer/login_attempt.html.erb +7 -0
  108. data/config/locales/common-ru.yml +5 -0
  109. data/config/locales/editable-pages-ru.yml +1 -1
  110. data/config/locales/regions-ru.yml +62 -0
  111. data/config/locales/users-ru.yml +48 -6
  112. data/config/routes.rb +19 -2
  113. data/db/migrate/20170301000201_create_regions.rb +32 -0
  114. data/db/migrate/20170302000001_create_users.rb +1 -0
  115. data/db/migrate/20170302000101_create_privileges.rb +2 -0
  116. data/db/migrate/20170302000102_create_user_privileges.rb +1 -0
  117. data/db/migrate/20170302000103_create_privilege_groups.rb +1 -0
  118. data/db/migrate/20170302000104_create_privilege_group_privileges.rb +4 -0
  119. data/db/migrate/20170629120000_create_login_attempts.rb +19 -0
  120. data/lib/biovision/base/engine.rb +6 -0
  121. data/lib/biovision/base/privilege_methods.rb +21 -3
  122. data/lib/biovision/base/version.rb +1 -1
  123. data/lib/tasks/{biovision/agents.rake → agents.rake} +0 -0
  124. data/lib/tasks/{biovision/browsers.rake → browsers.rake} +0 -0
  125. data/lib/tasks/{biovision/codes.rake → codes.rake} +0 -0
  126. data/lib/tasks/regions.rake +70 -0
  127. data/lib/tasks/{biovision/tokens.rake → tokens.rake} +0 -0
  128. data/lib/tasks/{biovision/users.rake → users.rake} +0 -0
  129. metadata +75 -17
  130. data/app/assets/stylesheets/biovision/base/fonts.scss +0 -9
  131. data/app/controllers/concerns/biovision/admin/privileges.rb +0 -34
  132. data/app/models/concerns/biovision/privilege_base.rb +0 -143
  133. data/app/models/concerns/biovision/user_base.rb +0 -124
  134. data/app/models/concerns/biovision/user_privilege_base.rb +0 -46
  135. data/app/views/admin/privilege_groups/_list.html.erb +0 -11
  136. data/app/views/authentication/_info.html.erb +0 -8
@@ -0,0 +1,8 @@
1
+ class UserMailer < ApplicationMailer
2
+ # @param [Integer] user_id
3
+ def login_attempt(user_id)
4
+ @user = User.find_by(id: user_id)
5
+
6
+ mail to: @user.email unless @user&.email.blank?
7
+ end
8
+ end
@@ -0,0 +1,49 @@
1
+ class CentralRegion
2
+ def id
3
+ nil
4
+ end
5
+
6
+ def name
7
+ I18n.t('activerecord.attributes.central_region.name')
8
+ end
9
+
10
+ def short_name
11
+ I18n.t('activerecord.attributes.central_region.short_name')
12
+ end
13
+
14
+ def locative
15
+ I18n.t('activerecord.attributes.central_region.locative')
16
+ end
17
+
18
+ def slug
19
+ ''
20
+ end
21
+
22
+ def long_slug
23
+ ''
24
+ end
25
+
26
+ def image
27
+ nil
28
+ end
29
+
30
+ def header_image
31
+ nil
32
+ end
33
+
34
+ def parents
35
+ []
36
+ end
37
+
38
+ def parent
39
+ nil
40
+ end
41
+
42
+ def parent_id
43
+ nil
44
+ end
45
+
46
+ def child_regions
47
+ []
48
+ end
49
+ end
@@ -8,6 +8,6 @@ module RequiredUniqueName
8
8
 
9
9
  scope :ordered_by_name, -> { order('name asc') }
10
10
  scope :with_name_like, ->(name) { where('name ilike ?', "%#{name}%") unless name.blank? }
11
- scope :with_name, ->(name) { where('name ilike ?', name) unless name.blank? }
11
+ scope :with_name, ->(name) { where('lower(name) = lower(?)', name) unless name.blank? }
12
12
  end
13
13
  end
@@ -7,6 +7,6 @@ module RequiredUniqueSlug
7
7
 
8
8
  scope :ordered_by_slug, -> { order('slug asc') }
9
9
  scope :with_slug_like, ->(slug) { where('slug ilike ?', "%#{slug}%") unless slug.blank? }
10
- scope :with_slug, ->(slug) { where('slug ilike ?', slug) unless slug.blank? }
10
+ scope :with_slug, ->(slug) { where('lower(slug) = lower(?)', slug) unless slug.blank? }
11
11
  end
12
12
  end
@@ -0,0 +1,24 @@
1
+ class LoginAttempt < ApplicationRecord
2
+ include HasOwner
3
+
4
+ PER_PAGE = 20
5
+
6
+ belongs_to :user
7
+ belongs_to :agent, optional: true
8
+
9
+ before_validation { self.password = password.to_s[0..254] }
10
+
11
+ scope :recent, -> { order('id desc') }
12
+ scope :since, ->(time) { where('created_at > ?', time)}
13
+
14
+ # @param [Integer] page
15
+ def self.page_for_administration(page = 1)
16
+ recent.page(page).per(PER_PAGE)
17
+ end
18
+
19
+ # @param [User] user
20
+ # @param [Integer] page
21
+ def self.page_for_owner(user, page = 1)
22
+ owned_by(user).recent.page(page).per(PER_PAGE)
23
+ end
24
+ end
@@ -1,3 +1,178 @@
1
1
  class Privilege < ApplicationRecord
2
- include Biovision::PrivilegeBase
2
+ include Toggleable
3
+
4
+ DESCRIPTION_LIMIT = 350
5
+ NAME_LIMIT = 250
6
+ SLUG_LIMIT = 250
7
+ PRIORITY_RANGE = (1..32767)
8
+
9
+ toggleable :regional
10
+
11
+ belongs_to :parent, class_name: Privilege.to_s, optional: true
12
+ has_many :children, class_name: Privilege.to_s, foreign_key: :parent_id
13
+ has_many :user_privileges, dependent: :destroy
14
+ has_many :users, through: :user_privileges
15
+ has_many :privilege_group_privileges, dependent: :destroy
16
+ has_many :privilege_groups, through: :privilege_group_privileges
17
+
18
+ after_initialize :set_next_priority
19
+
20
+ before_validation { self.name = name.strip unless name.nil? }
21
+ before_validation { self.slug = Canonizer.transliterate(name.to_s) if slug.blank? }
22
+ before_validation { self.regional = true if parent&.regional? }
23
+ before_validation :normalize_priority
24
+
25
+ before_save :compact_children_cache
26
+
27
+ validates_presence_of :name, :slug, :priority
28
+ validates :name, uniqueness: { case_sensitive: false, scope: [:parent_id] }
29
+ validates :slug, uniqueness: { case_sensitive: false }
30
+ validates_length_of :name, maximum: NAME_LIMIT
31
+ validates_length_of :slug, maximum: SLUG_LIMIT
32
+ validates_length_of :description, maximum: DESCRIPTION_LIMIT
33
+
34
+ scope :ordered_by_priority, -> { order('priority asc, name asc') }
35
+ scope :ordered_by_name, -> { order('name asc, slug asc') }
36
+ scope :visible, -> { where(visible: true, deleted: false) }
37
+ scope :for_tree, ->(parent_id = nil) { where(parent_id: parent_id).ordered_by_priority }
38
+ scope :siblings, ->(item) { where(parent_id: item.parent_id) }
39
+
40
+ def self.page_for_administration
41
+ ordered_by_name
42
+ end
43
+
44
+ def self.entity_parameters
45
+ %i(name slug priority description regional)
46
+ end
47
+
48
+ def self.creation_parameters
49
+ entity_parameters + %i(parent_id)
50
+ end
51
+
52
+ # @return [String]
53
+ def full_title
54
+ (parents.map(&:name) + [name]).join ' / '
55
+ end
56
+
57
+ # @return [Array<Integer>]
58
+ def ids
59
+ [id] + children_cache
60
+ end
61
+
62
+ # @return [Array<Integer>]
63
+ def branch_ids
64
+ parents_cache.split(',').map(&:to_i).reject { |i| i < 1 }.uniq + [id]
65
+ end
66
+
67
+ def parents
68
+ if parents_cache.blank?
69
+ []
70
+ else
71
+ Privilege.where(id: parents_cache.split(',').compact).order('id asc')
72
+ end
73
+ end
74
+
75
+ def cache_parents!
76
+ return if parent.nil?
77
+ self.parents_cache = "#{parent.parents_cache},#{parent_id}".gsub(/\A,/, '')
78
+ save!
79
+ end
80
+
81
+ def cache_children!
82
+ children.order('id asc').map do |child|
83
+ self.children_cache += [child.id] + child.children_cache
84
+ end
85
+ save!
86
+ parent&.cache_children!
87
+ end
88
+
89
+ def can_be_deleted?
90
+ children.count < 1
91
+ end
92
+
93
+ # @param [User] user
94
+ # @param [Region] region
95
+ def has_user?(user, region = nil)
96
+ return false if user.nil?
97
+ return true if user.super_user?
98
+ result = user_in_non_regional_branch?(user)
99
+
100
+ if regional? && !region.nil? && !result
101
+ result = user_in_regional_branch?(user, region)
102
+ end
103
+ result
104
+ end
105
+
106
+ # @param [User] user
107
+ # @param [Region] region
108
+ def grant(user, region = nil)
109
+ return if user.nil?
110
+ criteria = { privilege: self, user: user }
111
+ criteria[:region] = region if regional?
112
+ UserPrivilege.create(criteria) unless UserPrivilege.exists?(criteria)
113
+ end
114
+
115
+ # @param [User] user
116
+ # @param [Region] region
117
+ def revoke(user, region = nil)
118
+ return if user.nil?
119
+ criteria = { privilege: self, user: user }
120
+ criteria[:region] = region if regional?
121
+ UserPrivilege.where(criteria).delete_all
122
+ end
123
+
124
+ # @param [Integer] delta
125
+ def change_priority(delta)
126
+ new_priority = priority + delta
127
+ adjacent = self.class.siblings(self).find_by(priority: new_priority)
128
+ if adjacent.is_a?(self.class) && (adjacent.id != id)
129
+ adjacent.update!(priority: priority)
130
+ end
131
+ update(priority: new_priority)
132
+
133
+ self.class.for_tree(parent_id).map { |e| [e.id, e.priority] }.to_h
134
+ end
135
+
136
+ private
137
+
138
+ def set_next_priority
139
+ if id.nil? && priority == 1
140
+ self.priority = self.class.siblings(self).maximum(:priority).to_i + 1
141
+ end
142
+ end
143
+
144
+ def normalize_priority
145
+ self.priority = PRIORITY_RANGE.first if priority < PRIORITY_RANGE.first
146
+ self.priority = PRIORITY_RANGE.last if priority > PRIORITY_RANGE.last
147
+ end
148
+
149
+ def compact_children_cache
150
+ self.children_cache.uniq!
151
+ end
152
+
153
+ # @param [User] user
154
+ def user_in_non_regional_branch?(user)
155
+ selected_ids = Privilege.where(regional: false, id: branch_ids).pluck(:id)
156
+ if selected_ids.any?
157
+ UserPrivilege.exists?(privilege_id: selected_ids, user: user)
158
+ else
159
+ false
160
+ end
161
+ end
162
+
163
+ # @param [User] user
164
+ # @param [Region] region
165
+ def user_in_regional_branch?(user, region)
166
+ selected_ids = Privilege.where(regional: true, id: branch_ids).pluck(:id)
167
+ if selected_ids.any?
168
+ criteria = {
169
+ privilege_id: selected_ids,
170
+ region_id: region.branch_ids,
171
+ user: user
172
+ }
173
+ UserPrivilege.exists?(criteria)
174
+ else
175
+ false
176
+ end
177
+ end
3
178
  end
@@ -0,0 +1,100 @@
1
+ class Region < ApplicationRecord
2
+ include Toggleable
3
+
4
+ SLUG_PATTERN = /\A[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\z/
5
+ PER_PAGE = 20
6
+ NAME_LIMIT = 70
7
+ SLUG_LIMIT = 63
8
+
9
+ toggleable :visible
10
+
11
+ belongs_to :parent, class_name: Region.to_s, optional: true, touch: true
12
+ has_many :child_regions, class_name: Region.to_s, foreign_key: :parent_id
13
+ has_many :users, dependent: :nullify
14
+
15
+ mount_uploader :image, RegionImageUploader
16
+ mount_uploader :header_image, HeaderImageUploader
17
+
18
+ before_validation { self.slug = slug.to_s.downcase.strip }
19
+ before_save { self.children_cache.uniq! }
20
+ before_save :generate_long_slug
21
+
22
+ validates_presence_of :name, :slug
23
+ validates_uniqueness_of :name, :slug, scope: [:parent_id]
24
+ validates_length_of :name, maximum: NAME_LIMIT
25
+ validates_length_of :slug, maximum: SLUG_LIMIT
26
+ validates_format_of :slug, with: SLUG_PATTERN
27
+
28
+ scope :ordered_by_slug, -> { order('slug asc') }
29
+ scope :ordered_by_name, -> { order('name asc') }
30
+ scope :visible, -> { where(visible: true) }
31
+ scope :for_tree, -> (parent_id = nil) { where(parent_id: parent_id).ordered_by_name }
32
+
33
+ # @param [Integer] page
34
+ def self.page_for_administration(page = 1)
35
+ ordered_by_name.page(page).per(PER_PAGE)
36
+ end
37
+
38
+ def self.entity_parameters
39
+ %i(name short_name locative slug visible image header_image latitude longitude)
40
+ end
41
+
42
+ def self.creation_parameters
43
+ entity_parameters + %i(parent_id)
44
+ end
45
+
46
+ # @param [User] user
47
+ def editable_by?(user)
48
+ administrator = UserPrivilege.user_has_privilege?(user, :administrator)
49
+ manager = UserPrivilege.user_has_privilege?(user, :region_manager, self)
50
+ (administrator || manager) && !locked?
51
+ end
52
+
53
+ def parents
54
+ return [] if parents_cache.blank?
55
+ Region.where(id: parent_ids).order('id asc')
56
+ end
57
+
58
+ def parent_ids
59
+ parents_cache.split(',').compact
60
+ end
61
+
62
+ # @return [Array<Integer>]
63
+ def branch_ids
64
+ parents_cache.split(',').map(&:to_i).reject { |i| i < 1 }.uniq + [id]
65
+ end
66
+
67
+ def depth
68
+ parent_ids.count
69
+ end
70
+
71
+ def long_name
72
+ return name if parents.blank?
73
+ "#{parents.map(&:name).join('/')}/#{name}"
74
+ end
75
+
76
+ def cache_parents!
77
+ return if parent.nil?
78
+ self.parents_cache = "#{parent.parents_cache},#{parent_id}".gsub(/\A,/, '')
79
+ save!
80
+ end
81
+
82
+ def cache_children!
83
+ child_regions.order('id asc').each do |child|
84
+ self.children_cache += [child.id] + child.children_cache
85
+ end
86
+ save!
87
+ parent.cache_children! unless parent.nil?
88
+ end
89
+
90
+ private
91
+
92
+ def generate_long_slug
93
+ if parents_cache.blank?
94
+ self.long_slug = slug
95
+ else
96
+ slugs = Region.where(id: parent_ids).order('id asc').pluck(:slug) + [slug]
97
+ self.long_slug = slugs.join('-')
98
+ end
99
+ end
100
+ end
data/app/models/user.rb CHANGED
@@ -1,3 +1,120 @@
1
1
  class User < ApplicationRecord
2
- include Biovision::UserBase
2
+ include Toggleable
3
+
4
+ METRIC_REGISTRATION = 'users.registration.hit'
5
+ METRIC_AUTHENTICATION_SUCCESS = 'users.authentication.success.hit'
6
+ METRIC_AUTHENTICATION_FAILURE = 'users.authentication.failure.hit'
7
+ METRIC_AUTHENTICATION_EXTERNAL = 'users.authentication.external.hit'
8
+
9
+ PER_PAGE = 20
10
+
11
+ SLUG_LIMIT = 250
12
+ EMAIL_LIMIT = 250
13
+ NAME_LIMIT = 100
14
+ NOTICE_LIMIT = 255
15
+ PHONE_LIMIT = 50
16
+
17
+ toggleable %i(allow_login bot email_confirmed phone_confirmed allow_mail)
18
+
19
+ belongs_to :agent, optional: true
20
+
21
+ has_secure_password
22
+
23
+ mount_uploader :image, AvatarUploader
24
+
25
+ enum gender: [:female, :male]
26
+
27
+ belongs_to :agent, optional: true
28
+ belongs_to :inviter, class_name: User.to_s, optional: true
29
+ belongs_to :region, optional: true, counter_cache: true
30
+ has_many :invitees, class_name: User.to_s, foreign_key: :inviter_id, dependent: :nullify
31
+ has_many :tokens, dependent: :delete_all
32
+ has_many :codes, dependent: :delete_all
33
+ has_many :user_privileges, dependent: :destroy
34
+ has_many :privileges, through: :user_privileges
35
+ has_many :foreign_users, dependent: :delete_all
36
+ has_many :login_attempts, dependent: :delete_all
37
+
38
+ before_save :normalize_slug
39
+
40
+ validates_presence_of :screen_name, :email
41
+ validates_format_of :screen_name, with: /\A[a-z0-9_]{1,30}\z/i, if: :native_slug?
42
+ validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z0-9][-a-z0-9]+)\z/i
43
+ validates :screen_name, uniqueness: { case_sensitive: false }
44
+ validates :email, uniqueness: { case_sensitive: false }
45
+ validates_length_of :slug, maximum: SLUG_LIMIT
46
+ validates_length_of :screen_name, maximum: SLUG_LIMIT
47
+ validates_length_of :name, maximum: NAME_LIMIT
48
+ validates_length_of :patronymic, maximum: NAME_LIMIT
49
+ validates_length_of :surname, maximum: NAME_LIMIT
50
+ validates_length_of :email, maximum: EMAIL_LIMIT
51
+ validates_length_of :phone, maximum: PHONE_LIMIT
52
+ validates_length_of :notice, maximum: NOTICE_LIMIT
53
+
54
+ scope :with_privilege, ->(privilege) { joins(:user_privileges).where(user_privileges: { privilege_id: privilege.branch_ids }) }
55
+ scope :bots, ->(flag) { where(bot: flag.to_i > 0) unless flag.blank? }
56
+ scope :name_like, ->(val) { where('name ilike ?', "%#{val}%") unless val.blank? }
57
+ scope :email_like, ->(val) { where('email ilike ?', "%#{val}%") unless val.blank? }
58
+ scope :with_email, ->(email) { where('lower(email) = lower(?)', email) }
59
+ scope :screen_name_like, ->(val) { where('screen_name ilike ?', "%#{val}%") unless val.blank? }
60
+ scope :search, ->(q) { where("lower(concat_ws(' ', slug, email, surname, name)) like ?", "%#{q.downcase}%") unless q.blank? }
61
+ scope :filtered, ->(f) { name_like(f[:name]).email_like(f[:email]).screen_name_like(f[:screen_name]) }
62
+
63
+ # @param [Integer] page
64
+ # @param [Hash] filter
65
+ def self.page_for_administration(page, filter = {})
66
+ bots(filter[:bots]).filtered(filter).order('id desc').page(page).per(PER_PAGE)
67
+ end
68
+
69
+ def self.profile_parameters
70
+ %i(image name patronymic surname birthday gender allow_mail)
71
+ end
72
+
73
+ def self.sensitive_parameters
74
+ %i(email phone password password_confirmation)
75
+ end
76
+
77
+ # Параметры при регистрации
78
+ def self.new_profile_parameters
79
+ profile_parameters + sensitive_parameters + %i(screen_name)
80
+ end
81
+
82
+ # Параметры для администрирования
83
+ def self.entity_parameters
84
+ flags = %i(bot allow_login email_confirmed phone_confirmed foreign_slug)
85
+
86
+ new_profile_parameters + flags + %i(screen_name notice)
87
+ end
88
+
89
+ def self.ids_range
90
+ min = User.minimum(:id).to_i
91
+ max = User.maximum(:id).to_i
92
+ (min..max)
93
+ end
94
+
95
+ def profile_name
96
+ screen_name
97
+ end
98
+
99
+ def name_for_letter
100
+ name.blank? ? profile_name : name
101
+ end
102
+
103
+ def can_receive_letters?
104
+ allow_mail? && !email.blank?
105
+ end
106
+
107
+ def native_slug?
108
+ !foreign_slug?
109
+ end
110
+
111
+ private
112
+
113
+ def normalize_slug
114
+ if native_slug?
115
+ self.slug = screen_name.downcase
116
+ else
117
+ self.slug = slug.downcase
118
+ end
119
+ end
3
120
  end
@@ -1,3 +1,54 @@
1
1
  class UserPrivilege < ApplicationRecord
2
- include Biovision::UserPrivilegeBase
2
+ include HasOwner
3
+
4
+ belongs_to :user
5
+ belongs_to :privilege, counter_cache: :users_count
6
+ belongs_to :region, optional: true
7
+
8
+ validates_uniqueness_of :privilege_id, scope: [:user_id, :region_id]
9
+
10
+ # @param [User] user
11
+ # @return [Array<Integer>]
12
+ def self.ids(user)
13
+ privileges = user&.privileges
14
+ return [] if privileges.blank?
15
+ privileges.map(&:ids).flatten.uniq
16
+ end
17
+
18
+ # @param [User] user
19
+ # @param [String|Symbol] privilege_name
20
+ # @param [Region] region
21
+ def self.user_has_privilege?(user, privilege_name, region = nil)
22
+ return false if user.nil?
23
+ return true if user.super_user?
24
+ privilege = Privilege.find_by(slug: privilege_name)
25
+ privilege&.has_user?(user, region)
26
+ end
27
+
28
+ # @param [User] user
29
+ def self.user_has_any_privilege?(user)
30
+ return false if user.nil?
31
+ return true if user.super_user?
32
+ exists?(user: user)
33
+ end
34
+
35
+ # @param [User] user
36
+ # @param [Symbol] group_name
37
+ def self.user_in_group?(user, group_name)
38
+ return false if user.nil?
39
+ return true if user.super_user?
40
+ privilege_ids = PrivilegeGroup.ids(group_name)
41
+ return false if privilege_ids.blank?
42
+ exists?(user: user, privilege_id: privilege_ids)
43
+ end
44
+
45
+ private
46
+
47
+ def regional_ids(region)
48
+
49
+ end
50
+
51
+ def simple_ids
52
+
53
+ end
3
54
  end
@@ -15,7 +15,7 @@ class CodeManager::Confirmation < CodeManager
15
15
 
16
16
  def code_is_valid?
17
17
  return false if @code.nil?
18
- @code.owned_by?(@user) && @code.active? && @code.code_type == self.code_type
18
+ @code.active? && @code.code_type == self.class.code_type
19
19
  end
20
20
 
21
21
  def activate
@@ -0,0 +1,37 @@
1
+ class UserBouncer
2
+ # @param [User] user
3
+ # @param [Hash] tracking
4
+ def initialize(user, tracking)
5
+ @user = user
6
+ @tracking = tracking
7
+ end
8
+
9
+ # @param [String] password
10
+ def let_user_in?(password)
11
+ return false unless @user&.allow_login?
12
+ @password = password
13
+ too_many_attempts? ? (log_attempt && false) : try_password
14
+ end
15
+
16
+ private
17
+
18
+ def too_many_attempts?
19
+ LoginAttempt.owned_by(@user).since(15.minutes.ago).count > 4
20
+ end
21
+
22
+ def log_attempt
23
+ data = { user: @user, password: @password }
24
+ LoginAttempt.create(data.merge(@tracking))
25
+ end
26
+
27
+ def try_password
28
+ @user.authenticate(@password) || (count_attempt && false)
29
+ end
30
+
31
+ def count_attempt
32
+ log_attempt
33
+ if too_many_attempts?
34
+ UserMailer.login_attempt(@user.id).deliver_later
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ class HeaderImageUploader < CarrierWave::Uploader::Base
2
+ include CarrierWave::MiniMagick
3
+ include CarrierWave::BombShelter
4
+
5
+ storage :file
6
+
7
+ def max_pixel_dimensions
8
+ [4000, 4000]
9
+ end
10
+
11
+ # Override the directory where uploaded files will be stored.
12
+ # This is a sensible default for uploaders that are meant to be mounted:
13
+ def store_dir
14
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
15
+ end
16
+
17
+ # Provide a default URL as a default if there hasn't been a file uploaded:
18
+ # def default_url
19
+ # # For Rails 3.1+ asset pipeline compatibility:
20
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
21
+ #
22
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
23
+ # end
24
+
25
+ resize_to_limit 1000, 1000
26
+
27
+ version :medium do
28
+ resize_to_fit 500, 500
29
+ end
30
+
31
+ version :preview_2x, from_version: :medium do
32
+ resize_to_fit 160, 160
33
+ end
34
+
35
+ version :preview, from_version: :preview_2x do
36
+ resize_to_fit 80, 80
37
+ end
38
+
39
+ # Add a white list of extensions which are allowed to be uploaded.
40
+ # For images you might use something like this:
41
+ def extension_whitelist
42
+ %w(jpg jpeg png)
43
+ end
44
+
45
+ # Override the filename of the uploaded files:
46
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
47
+ # def filename
48
+ # "something.jpg" if original_filename
49
+ # end
50
+ end