biovision 0.1.210414.0 → 0.12.211128.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (217) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -40
  3. data/app/assets/stylesheets/biovision/admin/components/users.scss +4 -0
  4. data/app/assets/stylesheets/biovision/admin/components.scss +10 -0
  5. data/app/assets/stylesheets/biovision/admin/layout.scss +34 -8
  6. data/app/assets/stylesheets/biovision/biovision.scss +64 -26
  7. data/app/assets/stylesheets/biovision/components/carousel.scss +24 -24
  8. data/app/assets/stylesheets/biovision/components/filters.scss +39 -0
  9. data/app/assets/stylesheets/biovision/components/forms.scss +56 -10
  10. data/app/assets/stylesheets/biovision/components/quick_search.scss +24 -0
  11. data/app/assets/stylesheets/biovision/components.scss +1 -0
  12. data/app/assets/stylesheets/biovision/default.scss +4 -4
  13. data/app/assets/stylesheets/biovision/themes/default_theme/components/users/dashboard.scss +4 -0
  14. data/app/assets/stylesheets/biovision/themes/default_theme/components/users.scss +1 -0
  15. data/app/assets/stylesheets/biovision/themes/default_theme/layout/footer.scss +3 -0
  16. data/app/assets/stylesheets/biovision/themes/default_theme/layout/header.scss +12 -0
  17. data/app/assets/stylesheets/biovision/themes/default_theme/layout.scss +6 -3
  18. data/app/assets/stylesheets/biovision/themes/default_theme.scss +0 -1
  19. data/app/assets/stylesheets/biovision/vars.scss +5 -0
  20. data/app/controllers/admin/biovision_components_controller.rb +10 -0
  21. data/app/controllers/admin/components_controller.rb +33 -83
  22. data/app/controllers/admin/dynamic_pages_controller.rb +1 -1
  23. data/app/controllers/admin/index_controller.rb +8 -2
  24. data/app/controllers/admin/navigation_groups_controller.rb +31 -0
  25. data/app/controllers/admin/tokens_controller.rb +15 -0
  26. data/app/controllers/admin/users_controller.rb +35 -4
  27. data/app/controllers/admin_controller.rb +2 -9
  28. data/app/controllers/concerns/component_stories.rb +22 -0
  29. data/app/controllers/concerns/crud_entities.rb +23 -15
  30. data/app/controllers/concerns/my_crud_entities.rb +146 -0
  31. data/app/controllers/concerns/processed_forms.rb +28 -0
  32. data/app/controllers/concerns/restricted_access.rb +37 -0
  33. data/app/controllers/contact_controller.rb +1 -1
  34. data/app/controllers/errors_controller.rb +37 -0
  35. data/app/controllers/my/components_controller.rb +21 -0
  36. data/app/controllers/my/index_controller.rb +1 -3
  37. data/app/controllers/my/profiles_controller.rb +2 -0
  38. data/app/controllers/oembed_controller.rb +12 -0
  39. data/app/controllers/profile_controller.rb +2 -0
  40. data/app/controllers/users_controller.rb +2 -0
  41. data/app/helpers/biovision_components_helper.rb +7 -3
  42. data/app/helpers/biovision_helper.rb +33 -34
  43. data/app/helpers/entity_helper.rb +77 -0
  44. data/app/helpers/my_helper.rb +34 -0
  45. data/app/lib/biovision/components/base/component_parameters.rb +13 -2
  46. data/app/lib/biovision/components/base/component_privileges.rb +28 -18
  47. data/app/lib/biovision/components/base/component_settings.rb +8 -0
  48. data/app/lib/biovision/components/base/component_stories.rb +30 -0
  49. data/app/lib/biovision/components/base/entity_links.rb +38 -0
  50. data/app/lib/biovision/components/base/image_handling.rb +33 -0
  51. data/app/lib/biovision/components/base_component.rb +20 -49
  52. data/app/lib/biovision/components/contact_component.rb +5 -1
  53. data/app/lib/biovision/components/content/oembed/receiver.rb +98 -0
  54. data/app/lib/biovision/components/content/oembed/twitter_receiver.rb +20 -0
  55. data/app/lib/biovision/components/content/oembed/vimeo_receiver.rb +20 -0
  56. data/app/lib/biovision/components/content/oembed/youtube_receiver.rb +20 -0
  57. data/app/lib/biovision/components/content_component.rb +46 -9
  58. data/app/lib/biovision/components/track_component.rb +1 -1
  59. data/app/lib/biovision/components/users_component.rb +34 -2
  60. data/app/lib/biovision/helpers/data_helper.rb +70 -0
  61. data/app/lib/biovision/helpers/export_helper.rb +97 -0
  62. data/app/lib/biovision/migrations/component_migration.rb +56 -0
  63. data/app/lib/biovision/stories/component_story.rb +55 -0
  64. data/app/mailers/feedback_mailer.rb +14 -0
  65. data/app/models/biovision_component.rb +17 -1
  66. data/app/models/browser.rb +1 -1
  67. data/app/models/code.rb +5 -5
  68. data/app/models/concerns/checkable.rb +2 -1
  69. data/app/models/concerns/has_uploaded_file.rb +26 -0
  70. data/app/models/concerns/simple_tag.rb +30 -0
  71. data/app/models/concerns/toggleable.rb +2 -1
  72. data/app/models/concerns/tree_structure.rb +4 -1
  73. data/app/models/contact_method.rb +1 -1
  74. data/app/models/contact_type.rb +1 -1
  75. data/app/models/dynamic_block.rb +1 -1
  76. data/app/models/dynamic_page.rb +3 -1
  77. data/app/models/feedback_message.rb +7 -1
  78. data/app/models/feedback_response.rb +2 -2
  79. data/app/models/metric.rb +4 -0
  80. data/app/models/navigation_group.rb +11 -1
  81. data/app/models/oembed_domain.rb +25 -0
  82. data/app/models/oembed_link.rb +19 -0
  83. data/app/models/oembed_receiver.rb +15 -0
  84. data/app/models/role.rb +42 -12
  85. data/app/models/simple_image.rb +30 -3
  86. data/app/models/simple_image_tag.rb +1 -16
  87. data/app/models/token.rb +6 -2
  88. data/app/models/uploaded_file.rb +62 -0
  89. data/app/models/uploaded_file_tag.rb +15 -0
  90. data/app/models/uploaded_file_tag_file.rb +13 -0
  91. data/app/models/user.rb +35 -10
  92. data/app/models/user_role.rb +0 -1
  93. data/app/uploaders/simple_file_uploader.rb +2 -6
  94. data/app/uploaders/simple_image_uploader.rb +10 -21
  95. data/app/uploaders/uploaders/path_slug.rb +22 -0
  96. data/app/views/admin/agents/index.html.erb +1 -1
  97. data/app/views/admin/biovision_components/_nav_item.html.erb +6 -0
  98. data/app/views/admin/biovision_components/entity/_in_list.html.erb +12 -0
  99. data/app/views/admin/biovision_components/index.html.erb +11 -0
  100. data/app/views/admin/components/_list.html.erb +1 -1
  101. data/app/views/admin/components/entity/_links.html.erb +31 -21
  102. data/app/views/admin/components/links/_base.html.erb +1 -0
  103. data/app/views/admin/components/settings/_settings.html.erb +3 -3
  104. data/app/views/admin/components/settings.html.erb +2 -1
  105. data/app/views/admin/dynamic_blocks/_form.html.erb +1 -1
  106. data/app/views/admin/dynamic_blocks/entity/_in_list.html.erb +8 -6
  107. data/app/views/admin/dynamic_blocks/index.html.erb +6 -4
  108. data/app/views/admin/dynamic_blocks/show.html.erb +9 -7
  109. data/app/views/admin/dynamic_pages/_dynamic_page.jbuilder +18 -0
  110. data/app/views/admin/dynamic_pages/entity/_in_list.html.erb +6 -4
  111. data/app/views/admin/dynamic_pages/entity/_in_search.html.erb +7 -0
  112. data/app/views/admin/dynamic_pages/index.html.erb +6 -4
  113. data/app/views/admin/dynamic_pages/search.jbuilder +4 -0
  114. data/app/views/admin/dynamic_pages/show.html.erb +2 -2
  115. data/app/views/admin/index/index.html.erb +7 -5
  116. data/app/views/admin/ip_addresses/index.html.erb +2 -2
  117. data/app/views/admin/navigation_group_pages/entity/_in_list.html.erb +26 -0
  118. data/app/views/admin/navigation_groups/entity/_dynamic_pages.html.erb +38 -0
  119. data/app/views/admin/navigation_groups/entity/_in_list.html.erb +7 -5
  120. data/app/views/admin/navigation_groups/index.html.erb +6 -4
  121. data/app/views/admin/navigation_groups/show.html.erb +16 -3
  122. data/app/views/admin/tokens/_form.html.erb +31 -0
  123. data/app/views/admin/tokens/_nav_item.html.erb +6 -0
  124. data/app/views/admin/tokens/entity/_in_list.html.erb +27 -0
  125. data/app/views/admin/tokens/index.html.erb +11 -0
  126. data/app/views/admin/tokens/show.html.erb +26 -0
  127. data/app/views/admin/users/_user.jbuilder +18 -0
  128. data/app/views/admin/users/entity/_fields.html.erb +1 -1
  129. data/app/views/admin/users/entity/_in_list.html.erb +3 -3
  130. data/app/views/admin/users/entity/_in_search.html.erb +18 -0
  131. data/app/views/admin/users/index.html.erb +13 -4
  132. data/app/views/admin/users/roles/_component.html.erb +22 -0
  133. data/app/views/admin/users/roles.html.erb +23 -0
  134. data/app/views/admin/users/search.jbuilder +4 -0
  135. data/app/views/admin/users/show.html.erb +28 -10
  136. data/app/views/admin/widgets/_filters.html.erb +20 -0
  137. data/app/views/admin/widgets/_quick_search.html.erb +13 -0
  138. data/app/views/admin/widgets/filters/_flag.html.erb +15 -0
  139. data/app/views/admin/widgets/filters/_text.html.erb +7 -0
  140. data/app/views/application/unauthorized.html.erb +4 -1
  141. data/app/views/components/content/_dynamic_page.html.erb +6 -10
  142. data/app/views/components/content/_dynamic_page_content.html.erb +14 -0
  143. data/app/views/components/users/_login_form.html.erb +1 -0
  144. data/app/views/contact/_form.html.erb +1 -1
  145. data/app/views/errors/error.html.erb +1 -0
  146. data/app/views/feedback_mailer/new_feedback_request.html.erb +11 -0
  147. data/app/views/feedback_mailer/new_feedback_request.text.erb +6 -0
  148. data/app/views/index/index.html.erb +14 -0
  149. data/app/views/layouts/admin/_header.html.erb +7 -2
  150. data/app/views/layouts/admin.html.erb +0 -1
  151. data/app/views/layouts/application/_footer.html.erb +1 -1
  152. data/app/views/layouts/application/header/_authentication.html.erb +4 -1
  153. data/app/views/my/components/index.html.erb +25 -0
  154. data/app/views/my/components/show.html.erb +21 -0
  155. data/app/views/my/index/_cards.html.erb +15 -0
  156. data/app/views/my/index/_email.html.erb +14 -0
  157. data/app/views/my/index/_navigation.html.erb +33 -0
  158. data/app/views/my/index/index.html.erb +7 -26
  159. data/app/views/my/profiles/show.html.erb +13 -0
  160. data/app/views/{admin/components/links/extra/_content.html.erb → my/recoveries/show.html.erb} +0 -0
  161. data/app/views/shared/admin/_list.html.erb +10 -19
  162. data/app/views/shared/admin/_list_with_priority.html.erb +10 -19
  163. data/app/views/shared/admin/_priority.html.erb +6 -5
  164. data/app/views/shared/admin/_toggle.html.erb +5 -10
  165. data/app/views/shared/entity/_date_field.html.erb +6 -0
  166. data/app/views/shared/entity/_linked_entity.html.erb +2 -2
  167. data/app/views/shared/entity/_list.html.erb +22 -0
  168. data/app/views/shared/entity/_list_with_priority.html.erb +22 -0
  169. data/app/views/shared/entity/_parent.html.erb +1 -1
  170. data/app/views/shared/entity/_priority_icons.html.erb +8 -0
  171. data/app/views/shared/entity/_time_field.html.erb +6 -0
  172. data/app/views/shared/entity/_toggle.html.erb +12 -0
  173. data/app/views/shared/entity/_track.html.erb +12 -0
  174. data/app/views/shared/entity/_tree_caches.html.erb +8 -1
  175. data/app/views/shared/entity/edit.html.erb +10 -6
  176. data/app/views/shared/entity/new.html.erb +4 -2
  177. data/app/views/shared/forms/_field.html.erb +6 -2
  178. data/app/views/shared/forms/_field_with_search.html.erb +17 -0
  179. data/app/views/shared/forms/_meta_texts.html.erb +1 -1
  180. data/app/views/shared/forms/_simple_entity_link.html.erb +14 -0
  181. data/app/views/shared/forms/_simple_image.html.erb +12 -4
  182. data/app/views/shared/forms/_text_area.html.erb +1 -1
  183. data/app/views/shared/forms/_text_field.html.erb +1 -1
  184. data/app/views/shared/my/_list.html.erb +10 -19
  185. data/app/views/shared/my/_list_with_priority.html.erb +10 -19
  186. data/app/views/shared/my/entity/edit.html.erb +25 -0
  187. data/app/views/shared/my/entity/new.html.erb +18 -0
  188. data/app/views/simple_images/_simple_image.jbuilder +13 -0
  189. data/config/locales/biovision-ru.yml +18 -1
  190. data/config/locales/components-ru.yml +27 -4
  191. data/config/locales/contact-ru.yml +4 -0
  192. data/config/locales/content-ru.yml +12 -0
  193. data/config/locales/users-ru.yml +31 -6
  194. data/config/routes.rb +35 -4
  195. data/db/migrate/20191228000000_create_biovision_components.rb +2 -0
  196. data/db/migrate/20200224000000_create_track_component.rb +8 -12
  197. data/db/migrate/20200224000010_create_users_component.rb +8 -49
  198. data/db/migrate/20200404000000_create_simple_images.rb +1 -0
  199. data/db/migrate/20210405000000_create_acl.rb +15 -1
  200. data/db/migrate/{20200529000000_create_content_component.rb → 20210421000000_create_content_component.rb} +24 -18
  201. data/db/migrate/{20210401000000_create_contact_component.rb → 20210421000010_create_contact_component.rb} +1 -22
  202. data/db/migrate/20210616000000_create_uploaded_files.rb +52 -0
  203. data/db/migrate/amends/20210816060606_create_oembed_receivers.rb +21 -0
  204. data/db/migrate/amends/20210907070707_add_checksum_to_simple_images.rb +13 -0
  205. data/lib/biovision/base_methods.rb +8 -28
  206. data/lib/biovision/version.rb +1 -1
  207. data/lib/tasks/components.rake +51 -0
  208. metadata +90 -17
  209. data/app/lib/biovision/components/base/privilege_handler.rb +0 -79
  210. data/app/models/biovision_component_user.rb +0 -21
  211. data/app/views/admin/components/links/_content.html.erb +0 -9
  212. data/app/views/admin/components/links/_track.html.erb +0 -2
  213. data/app/views/admin/components/links/_users.html.erb +0 -4
  214. data/app/views/admin/components/privileges/_component_user.html.erb +0 -17
  215. data/app/views/admin/components/privileges/_links.html.erb +0 -17
  216. data/app/views/admin/components/privileges/_users.html.erb +0 -23
  217. data/app/views/admin/components/privileges.html.erb +0 -20
data/app/models/role.rb CHANGED
@@ -12,22 +12,25 @@ class Role < ApplicationRecord
12
12
  include Checkable
13
13
  include HasUuid
14
14
 
15
- SLUG_LIMIT = 50
16
- SLUG_PATTERN = /\A[a-z][_a-z]*[a-z]\z/.freeze
15
+ CACHE_KEY = 'role_cache'
17
16
 
18
17
  belongs_to :biovision_component
19
18
  has_many :role_groups, dependent: :destroy
20
- has_many :user_groups, dependent: :destroy
19
+ has_many :user_roles, dependent: :destroy
21
20
 
22
21
  before_validation { self.slug = slug.to_s.downcase }
23
22
 
24
23
  validates_presence_of :slug
25
24
  validates_uniqueness_of :slug, scope: :biovision_component_id
26
- validates_format_of :slug, with: SLUG_PATTERN
27
25
 
28
26
  # @param [String] slug
29
27
  def self.[](slug)
30
- find_by(slug: slug)
28
+ parts = slug.to_s.split('.')
29
+ criteria = {
30
+ biovision_components: { slug: parts.shift },
31
+ slug: parts.join('.')
32
+ }
33
+ joins(:biovision_component).where(criteria).first
31
34
  end
32
35
 
33
36
  def groups
@@ -37,20 +40,47 @@ class Role < ApplicationRecord
37
40
  end
38
41
 
39
42
  def users
40
- User.where(id: user_ids)
43
+ User.where("data->'role_cache' @> '[?]'::jsonb", id)
41
44
  end
42
45
 
43
46
  def user_ids
44
- # direct_inclusive = user_roles.where(inclusive: true).pluck(:user_id).uniq
45
- # direct_exclusive = user_roles.where(inclusive: false).pluck(:user_id).uniq
46
- # group_inclusive = groups.map(&:user_ids).flatten
47
- #
48
- # group_inclusive + direct_inclusive - direct_exclusive
49
- []
47
+ (user_roles.pluck(:user_id).uniq + groups.map(&:user_ids).flatten).uniq
50
48
  end
51
49
 
52
50
  def count_users!
53
51
  self.user_count = user_ids.count
54
52
  save
55
53
  end
54
+
55
+ # @param [User] user
56
+ def add_to_cache!(user)
57
+ return if user.nil?
58
+
59
+ ids = user.role_ids + [id]
60
+ user.data[CACHE_KEY] = ids.uniq
61
+ user.save
62
+ end
63
+
64
+ # @param [User] user
65
+ def remove_from_cache!(user)
66
+ return if user.nil?
67
+
68
+ new_ids = user.role_ids - [id]
69
+ user.data[CACHE_KEY] = new_ids
70
+ user.save
71
+ end
72
+
73
+ # @param [User] user
74
+ def add_user(user)
75
+ user_roles.create(user: user)
76
+ add_to_cache!(user)
77
+ end
78
+
79
+ # @param [User] user
80
+ def remove_user(user)
81
+ return if user.nil?
82
+
83
+ user_roles.where(user: user).destroy_all
84
+ remove_from_cache!(user)
85
+ end
56
86
  end
@@ -6,7 +6,9 @@
6
6
  # agent_id [Agent], optional
7
7
  # biovision_component_id [BiovisionComponent]
8
8
  # caption [string], optional
9
+ # checksum [String], optional
9
10
  # created_at [DateTime]
11
+ # data [jsonb]
10
12
  # image [SimpleImageUploader]
11
13
  # image_alt_text [string]
12
14
  # ip_address_id [IpAddress], optional
@@ -16,7 +18,6 @@
16
18
  # updated_at [DateTime]
17
19
  # user_id [User], optional
18
20
  # uuid [uuid]
19
- # data [jsonb]
20
21
  class SimpleImage < ApplicationRecord
21
22
  include Checkable
22
23
  include HasOwner
@@ -24,6 +25,7 @@ class SimpleImage < ApplicationRecord
24
25
  include HasUuid
25
26
 
26
27
  META_LIMIT = 255
28
+ ORIGINAL_CHECKSUM = 'original_checksum'
27
29
 
28
30
  mount_uploader :image, SimpleImageUploader
29
31
 
@@ -33,6 +35,8 @@ class SimpleImage < ApplicationRecord
33
35
  has_many :simple_image_tag_images, dependent: :destroy
34
36
  has_many :simple_image_tags, through: :simple_image_tag_images
35
37
 
38
+ before_save :calculate_checksum
39
+
36
40
  validates_presence_of :image
37
41
  validates_length_of :caption, maximum: META_LIMIT
38
42
  validates_length_of :image_alt_text, maximum: META_LIMIT
@@ -41,12 +45,27 @@ class SimpleImage < ApplicationRecord
41
45
 
42
46
  scope :in_component, ->(v) { where(biovision_component: v) }
43
47
  scope :filtered, ->(v) { where('image ilike ? or caption ilike ?', "%#{v}%", "%#{v}%") unless v.blank? }
44
- scope :list_for_administration, -> { order('image asc') }
48
+ scope :list_for_administration, -> { order(:image) }
45
49
 
46
- def self.entity_parameters(*)
50
+ # @param [String] input
51
+ def self.[](input)
52
+ case input
53
+ when /\A\h{8}-\h{4}-4\h{3}-[89ab]\h{3}-\h{12}\Z/i
54
+ find_by(uuid: input)
55
+ when /\A[a-f0-9]{64}\z/i
56
+ key = "data->>'#{ORIGINAL_CHECKSUM}'"
57
+ find_by(checksum: input) || find_by("#{key} = ?", input)
58
+ end
59
+ end
60
+
61
+ def self.entity_parameters
47
62
  %i[caption image image_alt_text source_link source_name]
48
63
  end
49
64
 
65
+ def self.json_attributes
66
+ %i[caption image_alt_text source_link source_name]
67
+ end
68
+
50
69
  def name
51
70
  File.basename(image.path)
52
71
  end
@@ -58,4 +77,12 @@ class SimpleImage < ApplicationRecord
58
77
  def image_slug
59
78
  "#{uuid[0..2]}/#{uuid[3..5]}/#{uuid}"
60
79
  end
80
+
81
+ private
82
+
83
+ def calculate_checksum
84
+ return if image&.path.blank?
85
+
86
+ self.checksum = Digest::SHA256.file(image.path).hexdigest
87
+ end
61
88
  end
@@ -9,22 +9,7 @@
9
9
  # updated_at [DateTime]
10
10
  class SimpleImageTag < ApplicationRecord
11
11
  include Checkable
12
- NAME_LIMIT = 100
12
+ include SimpleTag
13
13
 
14
14
  has_many :simple_image_tag_images, dependent: :delete_all
15
-
16
- before_validation :normalize_name
17
- validates_uniqueness_of :name, case_sensitive: false
18
-
19
- scope :list_for_administration, -> { order('name asc') }
20
-
21
- def self.entity_parameters(*)
22
- %i[name]
23
- end
24
-
25
- private
26
-
27
- def normalize_name
28
- self.name = name.to_s[0..NAME_LIMIT]
29
- end
30
15
  end
data/app/models/token.rb CHANGED
@@ -45,11 +45,11 @@ class Token < ApplicationRecord
45
45
  list_for_owner(user).page(page)
46
46
  end
47
47
 
48
- def self.entity_parameters(*)
48
+ def self.entity_parameters
49
49
  %i[active]
50
50
  end
51
51
 
52
- def self.creation_parameters(*)
52
+ def self.creation_parameters
53
53
  entity_parameters + %i[user_id]
54
54
  end
55
55
 
@@ -83,6 +83,10 @@ class Token < ApplicationRecord
83
83
  "[#{id}] #{user.profile_name}"
84
84
  end
85
85
 
86
+ def text_for_link
87
+ name
88
+ end
89
+
86
90
  # @param [User] user
87
91
  def editable_by?(user)
88
92
  return true if owned_by?(user)
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Uploaded file
4
+ #
5
+ # Attributes:
6
+ # attachment [SimpleFileUploader]
7
+ # agent_id [Agent], optional
8
+ # biovision_component_id [BiovisionComponent]
9
+ # checksum [String]
10
+ # created_at [DateTime]
11
+ # data [jsonb]
12
+ # description [String], optional
13
+ # ip_address_id [IpAddress], optional
14
+ # object_count [Integer]
15
+ # updated_at [DateTime]
16
+ # user_id [User], optional
17
+ # uuid [uuid]
18
+ class UploadedFile < ApplicationRecord
19
+ include Checkable
20
+ include HasOwner
21
+ include HasTrack
22
+ include HasUuid
23
+
24
+ META_LIMIT = 255
25
+
26
+ mount_uploader :attachment, SimpleFileUploader
27
+
28
+ belongs_to :agent, optional: true
29
+ belongs_to :biovision_component
30
+ belongs_to :user, optional: true
31
+ has_many :uploaded_file_tag_files, dependent: :destroy
32
+ has_many :uploaded_file_tags, through: :uploaded_file_tag_files
33
+
34
+ before_save :calculate_checksum
35
+
36
+ validates_presence_of :attachment
37
+ validates_length_of :description, maximum: META_LIMIT
38
+
39
+ scope :in_component, ->(v) { where(biovision_component: v) }
40
+ scope :filtered, ->(v) { where('description ilike ? or attachment ilike ?', "%#{v}%", "%#{v}%") unless v.blank? }
41
+ scope :list_for_administration, -> { order('attachment asc') }
42
+
43
+ def self.entity_parameters
44
+ %i[attachment description]
45
+ end
46
+
47
+ def name
48
+ File.basename(attachment.path)
49
+ end
50
+
51
+ def file_size
52
+ File.size(attachment.path)
53
+ end
54
+
55
+ private
56
+
57
+ def calculate_checksum
58
+ return if attachment&.path.blank?
59
+
60
+ self.checksum = Digest::SHA256.file(attachment.path).hexdigest
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Tag for uploaded file
4
+ #
5
+ # Attributes:
6
+ # created_at [DateTime]
7
+ # name [string]
8
+ # uploaded_files_count [integer]
9
+ # updated_at [DateTime]
10
+ class UploadedFileTag < ApplicationRecord
11
+ include Checkable
12
+ include SimpleTag
13
+
14
+ has_many :uploaded_file_tag_files, dependent: :delete_all
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Link between simple image and tag
4
+ #
5
+ # Attributes:
6
+ # uploaded_file_id [UploadedFile]
7
+ # uploaded_file_tag_id [UploadedFileTag]
8
+ class UploadedFileTagFile < ApplicationRecord
9
+ belongs_to :uploaded_file
10
+ belongs_to :uploaded_file_tag, counter_cache: :uploaded_files_count
11
+
12
+ validates_uniqueness_of :uploaded_file_tag_id, scope: :uploaded_file_id
13
+ end
data/app/models/user.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # User
4
- #
4
+ #
5
5
  # Attributes:
6
6
  # agent_id [Agent], optional
7
7
  # allow_mail [boolean]
@@ -20,14 +20,14 @@
20
20
  # last_seen [datetime], optional
21
21
  # notice [string], optional
22
22
  # password_digest [string]
23
+ # phone [string], optional
23
24
  # phone_confirmed [boolean]
24
25
  # primary_id [User], optional
25
26
  # profile [Jsonb]
27
+ # referral_link [string]
26
28
  # screen_name [string]
27
29
  # slug [string]
28
30
  # super_user [boolean]
29
- # phone [string], optional
30
- # referral_link [string]
31
31
  # updated_at [DateTime]
32
32
  # uuid [uuid]
33
33
  class User < ApplicationRecord
@@ -60,6 +60,8 @@ class User < ApplicationRecord
60
60
  has_many :foreign_users, dependent: :delete_all if Gem.loaded_specs.key?('biovision-oauth')
61
61
  has_many :login_attempts, dependent: :delete_all
62
62
  has_many :user_languages, dependent: :delete_all
63
+ has_many :user_roles, dependent: :destroy
64
+ has_many :user_groups, dependent: :destroy
63
65
 
64
66
  after_initialize :prepare_referral_link
65
67
 
@@ -71,7 +73,6 @@ class User < ApplicationRecord
71
73
  Biovision::Components::UsersComponent[entity].validate
72
74
  end
73
75
 
74
- validates_acceptance_of :consent
75
76
  validates :screen_name, presence: true, uniqueness: { case_sensitive: false }
76
77
  validates :email, uniqueness: { case_sensitive: false }, allow_nil: true
77
78
  validates :phone, uniqueness: { case_sensitive: false }, allow_nil: true
@@ -82,6 +83,7 @@ class User < ApplicationRecord
82
83
  scope :email_like, ->(v) { where('email ilike ?', "%#{v}%") unless v.blank? }
83
84
  scope :with_email, ->(v) { where('lower(email) = lower(?)', v.to_s) }
84
85
  scope :list_for_administration, -> { order('id desc') }
86
+ scope :search, ->(q) { where('screen_name ilike ?', "%#{q}%") unless q.blank? }
85
87
 
86
88
  def self.[](login)
87
89
  find_by(slug: login) || find_by_contact(login)
@@ -93,7 +95,7 @@ class User < ApplicationRecord
93
95
  end
94
96
 
95
97
  def self.profile_parameters
96
- %i[image allow_mail birthday consent]
98
+ %i[image allow_mail birthday]
97
99
  end
98
100
 
99
101
  def self.sensitive_parameters
@@ -110,12 +112,12 @@ class User < ApplicationRecord
110
112
  end
111
113
 
112
114
  # Parameters for registration
113
- def self.new_profile_parameters(*)
115
+ def self.new_profile_parameters
114
116
  profile_parameters + sensitive_parameters + %i[screen_name]
115
117
  end
116
118
 
117
119
  # Administrative parameters
118
- def self.entity_parameters(*)
120
+ def self.entity_parameters
119
121
  flags = %i[banned bot email_confirmed phone_confirmed]
120
122
 
121
123
  new_profile_parameters + flags + %i[notice screen_name slug]
@@ -131,9 +133,22 @@ class User < ApplicationRecord
131
133
  def role?(role_name)
132
134
  return true if super_user?
133
135
 
134
- parts = role_name.split('.')
135
- handler = Biovision::Components::BaseComponent.handler(parts.shift, self)
136
- handler.role?(parts.join)
136
+ role = Role[role_name]
137
+ role_ids.include?(role&.id)
138
+ end
139
+
140
+ def role_ids
141
+ Array(data[Role::CACHE_KEY]).map(&:to_i)
142
+ end
143
+
144
+ # @param [Role] role
145
+ def add_role(role)
146
+ role&.add_user(self)
147
+ end
148
+
149
+ # @param [Role] role
150
+ def remove_role(role)
151
+ role&.remove_user(self)
137
152
  end
138
153
 
139
154
  # Name to be shown as profile
@@ -191,6 +206,16 @@ class User < ApplicationRecord
191
206
  "/u/#{CGI.escape(key)}"
192
207
  end
193
208
 
209
+ def age
210
+ return if birthday.blank?
211
+
212
+ now = Time.now.utc.to_date
213
+ next_month = now.month > birthday.month
214
+ next_day = (now.month == birthday.month && now.day >= birthday.day)
215
+ delta = next_month || next_day ? 0 : 1
216
+ now.year - birthday.year - delta
217
+ end
218
+
194
219
  private
195
220
 
196
221
  def prepare_referral_link
@@ -4,7 +4,6 @@
4
4
  #
5
5
  # Attributes:
6
6
  # created_at [DateTime]
7
- # inclusive [Boolean]
8
7
  # role_id [Role]
9
8
  # updated_at [DateTime]
10
9
  # user_id [User]
@@ -2,13 +2,9 @@
2
2
 
3
3
  # Simple file uploader without any processing
4
4
  class SimpleFileUploader < CarrierWave::Uploader::Base
5
- storage :file
6
-
7
- def store_dir
8
- slug = "#{model.id / 100}/#{model.id}"
5
+ include Uploaders::PathSlug
9
6
 
10
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{slug}"
11
- end
7
+ storage :file
12
8
 
13
9
  def extension_blacklist
14
10
  %w[
@@ -2,21 +2,18 @@
2
2
 
3
3
  # Uploader for universal simple images
4
4
  class SimpleImageUploader < CarrierWave::Uploader::Base
5
+ include Uploaders::PathSlug
5
6
  include CarrierWave::ImageOptim
6
7
  include CarrierWave::MiniMagick
7
8
 
8
9
  storage :file
9
10
 
10
- def store_dir
11
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{path_slug}"
12
- end
13
-
14
11
  def default_url(*)
15
12
  ActionController::Base.helpers.asset_path('biovision/placeholders/1x1.svg')
16
13
  end
17
14
 
18
15
  process :auto_orient
19
- process optimize: [{ jpegoptim: true, optipng: true, svgo: true }]
16
+ process optimize: [{ jpegoptim: true, optipng: true, svgo: true }], if: :optimize_images?
20
17
 
21
18
  def auto_orient
22
19
  return unless raster?
@@ -50,10 +47,8 @@ class SimpleImageUploader < CarrierWave::Uploader::Base
50
47
  resize_to_fit(48, 48)
51
48
  end
52
49
 
53
- # Add a white list of extensions which are allowed to be uploaded.
54
- # For images you might use something like this:
55
- def extension_whitelist
56
- %w[jpg jpeg png svg svgz]
50
+ def extension_allowlist
51
+ [/jpe?g/, 'png', /svgz?/]
57
52
  end
58
53
 
59
54
  # Text for image alt attribute
@@ -74,6 +69,12 @@ class SimpleImageUploader < CarrierWave::Uploader::Base
74
69
  !new_file.extension.match?(/svgz?\z/i)
75
70
  end
76
71
 
72
+ def optimize_images?(*)
73
+ return false unless Rails.application.config.respond_to? :optimize_images
74
+
75
+ Rails.application.config.optimize_images
76
+ end
77
+
77
78
  def raster?
78
79
  !File.extname(path).match?(/\.svgz?\z/i)
79
80
  end
@@ -101,16 +102,4 @@ class SimpleImageUploader < CarrierWave::Uploader::Base
101
102
  def hd_url
102
103
  raster? ? hd.url : url
103
104
  end
104
-
105
- private
106
-
107
- def path_slug
108
- if model.respond_to?(:uuid)
109
- uuid = model&.uuid.to_s
110
- "#{uuid[0..2]}/#{uuid[3..5]}/#{uuid[6..7]}/#{uuid}"
111
- else
112
- id = model&.id.to_i
113
- "#{id / 1000}/#{id}"
114
- end
115
- end
116
105
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploaders
4
+ # Using UUID in file path when available
5
+ module PathSlug
6
+ def store_dir
7
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{path_slug}"
8
+ end
9
+
10
+ private
11
+
12
+ def path_slug
13
+ if model.respond_to?(:uuid)
14
+ uuid = model.uuid.to_s
15
+ "#{uuid[0..2]}/#{uuid[3..5]}/#{uuid[6..7]}/#{uuid}"
16
+ else
17
+ id = model&.id.to_i
18
+ "#{id / 1000}/#{id}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -7,5 +7,5 @@
7
7
  <article>
8
8
  <h1><%= t('.heading') %></h1>
9
9
 
10
- <%= render partial: 'shared/admin/list', locals: { collection: @collection } %>
10
+ <%= entity_list(@collection) %>
11
11
  </article>
@@ -0,0 +1,6 @@
1
+ <div>
2
+ <%= link_to t('.text'), admin_biovision_components_path %>
3
+ </div>
4
+ <div class="description">
5
+ <%= t('.description') %>
6
+ </div>
@@ -0,0 +1,12 @@
1
+ <div class="data">
2
+ <div><%= admin_entity_link(entity) %></div>
3
+ <div class="info"><%= entity.slug %></div>
4
+
5
+ <% if local_assigns[:handler]&.permit?('edit', entity) %>
6
+ <%= entity_toggle(entity) %>
7
+
8
+ <div class="entity-actions">
9
+ <%= entity_priority_icons(entity) %>
10
+ </div>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,11 @@
1
+ <% content_for :meta_title, t('.title') %>
2
+ <% content_for :breadcrumbs do %>
3
+ <%= admin_biovision_component_link(component_handler.component) %>
4
+ <span><%= t('admin.biovision_components.nav_item.text') %></span>
5
+ <% end %>
6
+
7
+ <article>
8
+ <h1><%= t('.heading') %></h1>
9
+
10
+ <%= entity_list(@collection, with_priority: true) %>
11
+ </article>
@@ -1,7 +1,7 @@
1
1
  <% collection.each do |component| %>
2
2
  <%
3
3
  handler = Biovision::Components::BaseComponent.handler(component, current_user)
4
- next if handler.component.nil? || !handler.permit?
4
+ next if handler.component.nil? || !handler.permit?('view')
5
5
  %>
6
6
  <%=
7
7
  render(
@@ -1,35 +1,45 @@
1
1
  <nav class="biovision-component-nav">
2
- <% if handler.permit?('settings') %>
3
- <% if handler.use_parameters? || handler.component.settings.any? %>
4
- <%=
5
- link_to(
6
- t('admin.components.settings.nav_text'),
7
- admin_component_settings_path(slug: handler.slug),
8
- class: 'settings'
9
- )
10
- %>
11
- <% end %>
12
- <% end %>
13
-
14
- <% if handler.administrator? %>
2
+ <% if handler.manage_settings? && handler.permit?('settings.view') %>
15
3
  <%=
16
4
  link_to(
17
- t('admin.components.privileges.nav_text'),
18
- admin_component_privileges_path(slug: handler.slug),
19
- class: 'privileges'
5
+ t('admin.components.settings.nav_text'),
6
+ admin_component_settings_path(slug: handler.slug),
7
+ class: 'settings'
20
8
  )
21
9
  %>
22
10
  <% end %>
23
11
 
24
12
  <% prefix = 'admin/components/links/' %>
25
- <% if lookup_context.exists?("#{prefix}_#{handler.slug}") %>
26
- <ul class="biovision-component-links">
13
+ <ul class="biovision-component-links">
14
+ <% if lookup_context.exists?("#{prefix}_#{handler.slug}") %>
15
+ <%=
16
+ render(
17
+ partial: "#{prefix}#{handler.slug}",
18
+ locals: { handler: handler }
19
+ )
20
+ %>
21
+ <% else %>
22
+ <% handler.administrative_parts.each do |part| %>
23
+ <% context = "admin/#{part}/_nav_item" %>
24
+ <% if handler.permit?("#{part}.view") && lookup_context.exists?(context) %>
25
+ <li>
26
+ <%=
27
+ render(
28
+ partial: "admin/#{part}/nav_item",
29
+ locals: { handler: handler }
30
+ )
31
+ %>
32
+ </li>
33
+ <% end %>
34
+ <% end %>
35
+ <% end %>
36
+ <% if lookup_context.exists?("admin/components/links/extra/_#{handler.slug}") %>
27
37
  <%=
28
38
  render(
29
- partial: "#{prefix}#{handler.slug}",
30
- locals: { handler: handler }
39
+ partial: "admin/components/links/extra/#{handler.slug}",
40
+ locals: { handler: handler }
31
41
  )
32
42
  %>
33
- </ul>
34
43
  <% end %>
44
+ </ul>
35
45
  </nav>
@@ -0,0 +1 @@
1
+ <li><%= render 'admin/biovision_components/nav_item' %></li>
@@ -1,4 +1,4 @@
1
- <% if settings.any? %>
1
+ <% if keys.any? %>
2
2
  <section class="biovision-component-settings">
3
3
  <h2><%= t('.heading') %></h2>
4
4
 
@@ -10,14 +10,14 @@
10
10
  ) do
11
11
  %>
12
12
  <div class="fields">
13
- <% settings.each do |key, value| %>
13
+ <% keys.each do |key| %>
14
14
  <%=
15
15
  render(
16
16
  partial: 'admin/components/settings/setting',
17
17
  locals: {
18
18
  slug: handler.slug,
19
19
  key: key,
20
- value: value
20
+ value: settings[key.to_s]
21
21
  }
22
22
  )
23
23
  %>