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.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/app/assets/images/biovision/base/placeholders/image.svg +1 -1
- data/app/assets/images/biovision/base/placeholders/region_image.svg +1 -0
- data/app/assets/stylesheets/biovision/base/admin.scss +226 -31
- data/app/assets/stylesheets/biovision/base/biovision.scss +45 -104
- data/app/assets/stylesheets/biovision/base/buttons.scss +3 -2
- data/app/assets/stylesheets/biovision/base/default.scss +66 -45
- data/app/assets/stylesheets/biovision/base/default_admin.scss +10 -0
- data/app/assets/stylesheets/biovision/base/default_application.scss +8 -0
- data/app/assets/stylesheets/biovision/base/filters.scss +4 -4
- data/app/assets/stylesheets/biovision/base/layout.scss +113 -0
- data/app/assets/stylesheets/biovision/base/message-box.scss +3 -3
- data/app/assets/stylesheets/biovision/base/regions.scss +9 -0
- data/app/assets/stylesheets/biovision/base/tootik.scss +6 -6
- data/app/assets/stylesheets/biovision/base/track.scss +7 -6
- data/app/assets/stylesheets/biovision/base/users.scss +64 -0
- data/app/controllers/admin/login_attempts_controller.rb +6 -0
- data/app/controllers/admin/privileges_controller.rb +37 -1
- data/app/controllers/admin/regions_controller.rb +37 -0
- data/app/controllers/admin/users_controller.rb +2 -2
- data/app/controllers/authentication_controller.rb +27 -26
- data/app/controllers/concerns/authentication.rb +20 -0
- data/app/controllers/my/confirmations_controller.rb +8 -3
- data/app/controllers/my/login_attempts_controller.rb +9 -0
- data/app/controllers/my/profiles_controller.rb +10 -2
- data/app/controllers/my/tokens_controller.rb +20 -0
- data/app/controllers/regions_controller.rb +73 -0
- data/app/helpers/biovision_regions_helper.rb +22 -0
- data/app/mailers/application_mailer.rb +5 -0
- data/app/mailers/user_mailer.rb +8 -0
- data/app/models/central_region.rb +49 -0
- data/app/models/concerns/required_unique_name.rb +1 -1
- data/app/models/concerns/required_unique_slug.rb +1 -1
- data/app/models/login_attempt.rb +24 -0
- data/app/models/privilege.rb +176 -1
- data/app/models/region.rb +100 -0
- data/app/models/user.rb +118 -1
- data/app/models/user_privilege.rb +52 -1
- data/app/services/code_manager/confirmation.rb +1 -1
- data/app/services/user_bouncer.rb +37 -0
- data/app/uploaders/header_image_uploader.rb +50 -0
- data/app/uploaders/region_image_uploader.rb +53 -0
- data/app/views/admin/agents/entity/_preview.jbuilder +8 -0
- data/app/views/admin/codes/index.html.erb +1 -1
- data/app/views/admin/editable_pages/index.html.erb +1 -1
- data/app/views/admin/index/index.html.erb +4 -0
- data/app/views/admin/login_attempts/_nav_item.html.erb +6 -0
- data/app/views/admin/login_attempts/entity/_in_list.html.erb +16 -0
- data/app/views/admin/login_attempts/included/_agents.jbuilder +3 -0
- data/app/views/admin/login_attempts/included/_users.jbuilder +3 -0
- data/app/views/admin/login_attempts/index.html.erb +16 -0
- data/app/views/admin/login_attempts/index.jbuilder +28 -0
- data/app/views/admin/privilege_groups/index.html.erb +1 -1
- data/app/views/admin/privileges/_toggleable.html.erb +7 -0
- data/app/views/admin/privileges/entity/_in_list.html.erb +1 -0
- data/app/views/admin/privileges/entity/_region.html.erb +12 -0
- data/app/views/admin/privileges/regions.jbuilder +10 -0
- data/app/views/admin/privileges/show.html.erb +2 -0
- data/app/views/admin/regions/_nav_item.html.erb +2 -0
- data/app/views/admin/regions/_toggleable.html.erb +7 -0
- data/app/views/admin/regions/entity/_in_list.html.erb +32 -0
- data/app/views/admin/regions/index.html.erb +22 -0
- data/app/views/admin/regions/show.html.erb +95 -0
- data/app/views/admin/tokens/index.html.erb +1 -1
- data/app/views/admin/users/_search.html.erb +1 -1
- data/app/views/admin/users/entity/_preview.jbuilder +12 -0
- data/app/views/admin/users/entity/_privilege.html.erb +23 -7
- data/app/views/admin/users/entity/_privilege_tree.html.erb +2 -2
- data/app/views/admin/users/privileges.html.erb +47 -12
- data/app/views/admin/users/show.html.erb +17 -1
- data/app/views/admin/users/tokens.html.erb +1 -1
- data/app/views/authentication/new.html.erb +1 -2
- data/app/views/layouts/admin/_footer.html.erb +8 -0
- data/app/views/layouts/application/_footer.html.erb +5 -0
- data/app/views/layouts/application/_header.html.erb +9 -0
- data/app/views/layouts/application/header/_authentication.html.erb +7 -0
- data/app/views/layouts/application/header/_logo.html.erb +3 -0
- data/app/views/layouts/application/header/_navigation.html.erb +0 -0
- data/app/views/layouts/application/header/authentication/_links.html.erb +4 -0
- data/app/views/layouts/application/header/authentication/_plate.html.erb +4 -0
- data/app/views/layouts/mailer.html.erb +13 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/my/confirmations/show.html.erb +11 -1
- data/app/views/my/index/index.html.erb +6 -4
- data/app/views/{admin/tokens → my/login_attempts}/_list.html.erb +1 -1
- data/app/views/my/login_attempts/_nav_item.html.erb +6 -0
- data/app/views/my/login_attempts/entity/_in_list.html.erb +13 -0
- data/app/views/my/login_attempts/included/_agents.jbuilder +7 -0
- data/app/views/my/login_attempts/index.html.erb +13 -0
- data/app/views/my/login_attempts/index.jbuilder +22 -0
- data/app/views/my/profiles/_nav_item.html.erb +6 -0
- data/app/views/my/profiles/new/_form.html.erb +49 -23
- data/app/views/my/profiles/new.html.erb +2 -2
- data/app/views/{admin/codes → my/tokens}/_list.html.erb +1 -1
- data/app/views/my/tokens/_nav_item.html.erb +6 -0
- data/app/views/my/tokens/_toggleable.html.erb +7 -0
- data/app/views/my/tokens/entity/_in_list.html.erb +18 -0
- data/app/views/my/tokens/index.html.erb +13 -0
- data/app/views/privileges/_form.html.erb +7 -0
- data/app/views/regions/_form.html.erb +73 -0
- data/app/views/regions/edit.html.erb +20 -0
- data/app/views/regions/new.html.erb +17 -0
- data/app/views/shared/_counters.html.erb +0 -0
- data/app/views/shared/_pagination.jbuilder +9 -0
- data/app/views/{admin/editable_pages → shared/admin}/_list.html.erb +2 -2
- data/app/views/user_mailer/login_attempt.html.erb +7 -0
- data/config/locales/common-ru.yml +5 -0
- data/config/locales/editable-pages-ru.yml +1 -1
- data/config/locales/regions-ru.yml +62 -0
- data/config/locales/users-ru.yml +48 -6
- data/config/routes.rb +19 -2
- data/db/migrate/20170301000201_create_regions.rb +32 -0
- data/db/migrate/20170302000001_create_users.rb +1 -0
- data/db/migrate/20170302000101_create_privileges.rb +2 -0
- data/db/migrate/20170302000102_create_user_privileges.rb +1 -0
- data/db/migrate/20170302000103_create_privilege_groups.rb +1 -0
- data/db/migrate/20170302000104_create_privilege_group_privileges.rb +4 -0
- data/db/migrate/20170629120000_create_login_attempts.rb +19 -0
- data/lib/biovision/base/engine.rb +6 -0
- data/lib/biovision/base/privilege_methods.rb +21 -3
- data/lib/biovision/base/version.rb +1 -1
- data/lib/tasks/{biovision/agents.rake → agents.rake} +0 -0
- data/lib/tasks/{biovision/browsers.rake → browsers.rake} +0 -0
- data/lib/tasks/{biovision/codes.rake → codes.rake} +0 -0
- data/lib/tasks/regions.rake +70 -0
- data/lib/tasks/{biovision/tokens.rake → tokens.rake} +0 -0
- data/lib/tasks/{biovision/users.rake → users.rake} +0 -0
- metadata +75 -17
- data/app/assets/stylesheets/biovision/base/fonts.scss +0 -9
- data/app/controllers/concerns/biovision/admin/privileges.rb +0 -34
- data/app/models/concerns/biovision/privilege_base.rb +0 -143
- data/app/models/concerns/biovision/user_base.rb +0 -124
- data/app/models/concerns/biovision/user_privilege_base.rb +0 -46
- data/app/views/admin/privilege_groups/_list.html.erb +0 -11
- data/app/views/authentication/_info.html.erb +0 -8
@@ -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
|
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
|
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
|
data/app/models/privilege.rb
CHANGED
@@ -1,3 +1,178 @@
|
|
1
1
|
class Privilege < ApplicationRecord
|
2
|
-
include
|
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
|
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
|
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.
|
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
|