decidim-admin 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/admin/sort_steps.js.es6 +3 -3
  3. data/app/assets/stylesheets/decidim/admin/_email_preview.scss +5 -0
  4. data/app/assets/stylesheets/decidim/admin/application.scss +1 -0
  5. data/app/commands/decidim/admin/create_newsletter.rb +30 -0
  6. data/app/commands/decidim/admin/create_participatory_process_admin.rb +3 -0
  7. data/app/commands/decidim/admin/deliver_newsletter.rb +29 -0
  8. data/app/commands/decidim/admin/destroy_participatory_process_step.rb +9 -0
  9. data/app/commands/decidim/admin/update_feature_permissions.rb +46 -0
  10. data/app/commands/decidim/admin/update_newsletter.rb +33 -0
  11. data/app/commands/decidim/admin/update_organization.rb +17 -2
  12. data/app/commands/decidim/admin/update_participatory_process.rb +4 -2
  13. data/app/constraints/decidim/admin/organization_dashboard_constraint.rb +1 -1
  14. data/app/controllers/decidim/admin/application_controller.rb +3 -0
  15. data/app/controllers/decidim/admin/feature_permissions_controller.rb +59 -0
  16. data/app/controllers/decidim/admin/features_controller.rb +20 -0
  17. data/app/controllers/decidim/admin/newsletters_controller.rb +115 -0
  18. data/app/controllers/decidim/admin/participatory_processes_controller.rb +4 -4
  19. data/app/controllers/decidim/admin/users_controller.rb +3 -1
  20. data/app/forms/decidim/admin/newsletter_form.rb +15 -0
  21. data/app/forms/decidim/admin/organization_form.rb +10 -0
  22. data/app/forms/decidim/admin/participatory_process_form.rb +8 -5
  23. data/app/forms/decidim/admin/permission_form.rb +31 -0
  24. data/app/forms/decidim/admin/permissions_form.rb +16 -0
  25. data/app/helpers/decidim/admin/application_helper.rb +1 -0
  26. data/app/jobs/decidim/admin/newsletter_delivery_job.rb +18 -0
  27. data/app/jobs/decidim/admin/newsletter_job.rb +32 -0
  28. data/app/models/decidim/admin/abilities/admin_user.rb +4 -0
  29. data/app/models/decidim/admin/abilities/base.rb +4 -0
  30. data/app/models/decidim/admin/abilities/collaborator_user.rb +19 -0
  31. data/app/models/decidim/admin/abilities/participatory_process_admin.rb +1 -1
  32. data/app/models/decidim/admin/participatory_process_user_role.rb +1 -1
  33. data/app/queries/decidim/admin/manageable_participatory_processes_for_user.rb +1 -1
  34. data/app/views/decidim/admin/attachments/edit.html.erb +1 -1
  35. data/app/views/decidim/admin/attachments/new.html.erb +1 -1
  36. data/app/views/decidim/admin/categories/edit.html.erb +1 -1
  37. data/app/views/decidim/admin/categories/new.html.erb +1 -1
  38. data/app/views/decidim/admin/dashboard/show.html.erb +1 -1
  39. data/app/views/decidim/admin/feature_permissions/edit.html.erb +27 -0
  40. data/app/views/decidim/admin/features/_feature.html.erb +10 -0
  41. data/app/views/decidim/admin/features/edit.html.erb +1 -1
  42. data/app/views/decidim/admin/features/new.html.erb +1 -1
  43. data/app/views/decidim/admin/newsletters/_form.html.erb +5 -0
  44. data/app/views/decidim/admin/newsletters/edit.html.erb +11 -0
  45. data/app/views/decidim/admin/newsletters/index.html.erb +47 -0
  46. data/app/views/decidim/admin/newsletters/new.html.erb +11 -0
  47. data/app/views/decidim/admin/newsletters/show.html.erb +14 -0
  48. data/app/views/decidim/admin/organization/_form.html.erb +34 -0
  49. data/app/views/decidim/admin/organization/edit.html.erb +2 -2
  50. data/app/views/decidim/admin/participatory_process_steps/_form.html.erb +1 -1
  51. data/app/views/decidim/admin/participatory_process_steps/edit.html.erb +1 -1
  52. data/app/views/decidim/admin/participatory_process_steps/new.html.erb +1 -1
  53. data/app/views/decidim/admin/participatory_process_user_roles/index.html.erb +1 -1
  54. data/app/views/decidim/admin/participatory_processes/_form.html.erb +18 -5
  55. data/app/views/decidim/admin/participatory_processes/edit.html.erb +1 -1
  56. data/app/views/decidim/admin/participatory_processes/index.html.erb +1 -1
  57. data/app/views/decidim/admin/participatory_processes/new.html.erb +2 -2
  58. data/app/views/decidim/admin/participatory_processes/show.html.erb +5 -2
  59. data/app/views/decidim/admin/scopes/edit.html.erb +2 -2
  60. data/app/views/decidim/admin/scopes/index.html.erb +1 -1
  61. data/app/views/decidim/admin/scopes/new.html.erb +2 -2
  62. data/app/views/decidim/admin/static_pages/_form.html.erb +1 -1
  63. data/app/views/decidim/admin/static_pages/edit.html.erb +2 -2
  64. data/app/views/decidim/admin/static_pages/index.html.erb +1 -1
  65. data/app/views/decidim/admin/static_pages/new.html.erb +2 -2
  66. data/app/views/decidim/admin/static_pages/show.html.erb +1 -1
  67. data/app/views/decidim/admin/user_groups/index.html.erb +1 -1
  68. data/app/views/decidim/admin/users/index.html.erb +1 -1
  69. data/app/views/decidim/admin/users/new.html.erb +2 -2
  70. data/app/views/layouts/decidim/admin/_sidebar.html.erb +1 -0
  71. data/app/views/layouts/decidim/admin/participatory_process.html.erb +1 -1
  72. data/config/i18n-tasks.yml +4 -3
  73. data/config/locales/ca.yml +60 -1
  74. data/config/locales/en.yml +59 -0
  75. data/config/locales/es.yml +59 -0
  76. data/config/locales/eu.yml +5 -0
  77. data/config/routes.rb +15 -1
  78. data/db/seeds.rb +3 -1
  79. data/lib/decidim/admin/engine.rb +1 -0
  80. data/lib/decidim/admin/features/base_controller.rb +5 -1
  81. metadata +26 -20
@@ -24,9 +24,9 @@ module Decidim
24
24
  @form = form(ParticipatoryProcessForm).from_params(params)
25
25
 
26
26
  CreateParticipatoryProcess.call(@form) do
27
- on(:ok) do
27
+ on(:ok) do |participatory_process|
28
28
  flash[:notice] = I18n.t("participatory_processes.create.success", scope: "decidim.admin")
29
- redirect_to participatory_processes_path
29
+ redirect_to participatory_process_path(participatory_process)
30
30
  end
31
31
 
32
32
  on(:invalid) do
@@ -48,9 +48,9 @@ module Decidim
48
48
  @form = form(ParticipatoryProcessForm).from_params(params)
49
49
 
50
50
  UpdateParticipatoryProcess.call(@participatory_process, @form) do
51
- on(:ok) do
51
+ on(:ok) do |participatory_process|
52
52
  flash[:notice] = I18n.t("participatory_processes.update.success", scope: "decidim.admin")
53
- redirect_to participatory_processes_path
53
+ redirect_to participatory_process_path(participatory_process)
54
54
  end
55
55
 
56
56
  on(:invalid) do
@@ -23,7 +23,9 @@ module Decidim
23
23
  organization: current_organization,
24
24
  invitation_instructions: "invite_admin",
25
25
  roles: %w(admin),
26
- invited_by: current_user
26
+ invited_by: current_user,
27
+ comments_notifications: true,
28
+ replies_notifications: true
27
29
  }
28
30
 
29
31
  @form = form(InviteAdminForm).from_params(params.merge(default_params))
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Admin
4
+ # The form that validates the data to construct a valid Newsletter.
5
+ class NewsletterForm < Decidim::Form
6
+ mimic :newsletter
7
+
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :subject, String
11
+ translatable_attribute :body, String
12
+ validates :subject, :body, translatable_presence: true
13
+ end
14
+ end
15
+ end
@@ -11,10 +11,17 @@ module Decidim
11
11
 
12
12
  attribute :name, String
13
13
  attribute :twitter_handler, String
14
+ attribute :facebook_handler, String
15
+ attribute :instagram_handler, String
16
+ attribute :youtube_handler, String
17
+ attribute :github_handler, String
14
18
  attribute :default_locale, String
15
19
  attribute :homepage_image
16
20
  attribute :logo
17
21
  attribute :favicon
22
+ attribute :official_url
23
+ attribute :official_img_header
24
+ attribute :official_img_footer
18
25
  attribute :show_statistics
19
26
 
20
27
  translatable_attribute :description, String
@@ -24,6 +31,9 @@ module Decidim
24
31
  validates :default_locale, presence: true
25
32
  validates :default_locale, inclusion: { in: :available_locales }
26
33
 
34
+ validates :official_img_header, file_size: { less_than_or_equal_to: ->(_record) { Decidim.maximum_attachment_size } }, file_content_type: { allow: ["image/jpeg", "image/png"] }
35
+ validates :official_img_footer, file_size: { less_than_or_equal_to: ->(_record) { Decidim.maximum_attachment_size } }, file_content_type: { allow: ["image/jpeg", "image/png"] }
36
+
27
37
  private
28
38
 
29
39
  def available_locales
@@ -11,13 +11,16 @@ module Decidim
11
11
  translatable_attribute :subtitle, String
12
12
  translatable_attribute :description, String
13
13
  translatable_attribute :short_description, String
14
- translatable_attribute :domain, String
15
14
  translatable_attribute :scope, String
15
+ translatable_attribute :developer_group, String
16
+ translatable_attribute :local_area, String
17
+ translatable_attribute :target, String
18
+ translatable_attribute :participatory_scope, String
19
+ translatable_attribute :participatory_structure, String
16
20
 
17
21
  mimic :participatory_process
18
22
 
19
23
  attribute :end_date, Date
20
- attribute :developer_group, String
21
24
  attribute :slug, String
22
25
  attribute :hashtag, String
23
26
  attribute :promoted, Boolean
@@ -29,10 +32,10 @@ module Decidim
29
32
 
30
33
  validate :slug, :slug_uniqueness
31
34
 
32
- validates :hero_image, file_size: { less_than_or_equal_to: lambda { |_record| Decidim.maximum_attachment_size } }, file_content_type: { allow: ['image/jpeg', 'image/png'] }
33
- validates :banner_image, file_size: { less_than_or_equal_to:lambda { |_record| Decidim.maximum_attachment_size } }, file_content_type: { allow: ['image/jpeg', 'image/png'] }
35
+ validates :hero_image, file_size: { less_than_or_equal_to: ->(_record) { Decidim.maximum_attachment_size } }, file_content_type: { allow: ["image/jpeg", "image/png"] }
36
+ validates :banner_image, file_size: { less_than_or_equal_to: ->(_record) { Decidim.maximum_attachment_size } }, file_content_type: { allow: ["image/jpeg", "image/png"] }
34
37
 
35
- private
38
+ private
36
39
 
37
40
  def slug_uniqueness
38
41
  return unless current_organization.participatory_processes.where(slug: slug).where.not(id: id).any?
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Admin
4
+ # This form handles permissions for a particular action in the admin panel.
5
+ class PermissionForm < Form
6
+ attribute :authorization_handler_name, String
7
+ attribute :options, String
8
+
9
+ validate :sanitize
10
+ validate :options_is_valid_json
11
+
12
+ private
13
+
14
+ def sanitize
15
+ self.authorization_handler_name = nil if authorization_handler_name.blank?
16
+ self.options = nil if authorization_handler_name.blank?
17
+ self.options = nil if options.blank?
18
+ end
19
+
20
+ def options_is_valid_json
21
+ return unless options
22
+
23
+ result = JSON.parse(options)
24
+ errors.add(:options, :invalid_json) unless result.is_a?(Hash)
25
+ result
26
+ rescue JSON::ParserError
27
+ errors.add(:options, :invalid_json)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Admin
4
+ # This form handles a set of forms related to handling permissions
5
+ # in the admin panel.
6
+ class PermissionsForm < Form
7
+ mimic :feature_permissions
8
+
9
+ attribute :permissions, Hash[String => PermissionForm]
10
+
11
+ def valid?
12
+ super && permissions.values.all?(&:valid?)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -8,6 +8,7 @@ module Decidim
8
8
  include Decidim::TranslationsHelper
9
9
  include Decidim::HumanizeBooleansHelper
10
10
  include Decidim::AriaSelectedLinkToHelper
11
+ include Decidim::MetaTagsHelper
11
12
 
12
13
  def title
13
14
  current_organization.name
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Admin
4
+ # Custom ApplicationJob scoped to the admin panel.
5
+ #
6
+ class NewsletterDeliveryJob < ApplicationJob
7
+ queue_as :newsletter
8
+
9
+ def perform(user, newsletter)
10
+ NewsletterMailer.newsletter(user, newsletter).deliver_now
11
+
12
+ newsletter.with_lock do
13
+ newsletter.increment!(:total_deliveries)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Admin
4
+ # Custom ApplicationJob scoped to the admin panel.
5
+ #
6
+ class NewsletterJob < ApplicationJob
7
+ queue_as :newsletter
8
+
9
+ def perform(newsletter)
10
+ newsletter.with_lock do
11
+ raise "Newsletter already sent" if newsletter.sent?
12
+
13
+ newsletter.update_attributes!(
14
+ sent_at: Time.current,
15
+ total_recipients: recipients.count,
16
+ total_deliveries: 0
17
+ )
18
+ end
19
+
20
+ recipients.find_each do |user|
21
+ NewsletterDeliveryJob.perform_later(user, newsletter)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def recipients
28
+ @recipients ||= User.where(newsletter_notifications: true).where.not(email: nil)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -15,9 +15,11 @@ module Decidim
15
15
  can :manage, Category
16
16
  can :manage, ParticipatoryProcessUserRole
17
17
  can [:create, :update, :index, :new, :read], StaticPage
18
+
18
19
  can [:update_slug, :destroy], [StaticPage, StaticPageForm] do |page|
19
20
  !StaticPage.default?(page.slug)
20
21
  end
22
+
21
23
  can [:read, :update], Decidim::Organization do |organization|
22
24
  organization == user.organization
23
25
  end
@@ -27,7 +29,9 @@ module Decidim
27
29
  can :manage, :admin_users
28
30
  can :manage, Attachment
29
31
  can :manage, Scope
32
+ can :manage, Newsletter
30
33
  can [:create, :index, :new, :read, :invite], User
34
+
31
35
  can [:destroy], [User] do |user_to_destroy|
32
36
  user != user_to_destroy
33
37
  end
@@ -12,6 +12,10 @@ module Decidim
12
12
  def initialize(user, context)
13
13
  merge ::Decidim::Ability.new(user, context)
14
14
  merge ParticipatoryProcessAdmin.new(user, context)
15
+
16
+ Decidim.admin_abilities.each do |ability|
17
+ merge ability.constantize.new(user, context)
18
+ end
15
19
  end
16
20
  end
17
21
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Admin
4
+ module Abilities
5
+ # Defines the abilities for a collaborator user in the admin
6
+ # section. Intended to be used with `cancancan`.
7
+ class CollaboratorUser
8
+ include CanCan::Ability
9
+
10
+ def initialize(user, _context)
11
+ return unless user && user.role?(:collaborator)
12
+
13
+ can :manage, :admin_dashboard
14
+ can :preview, ParticipatoryProcess
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -14,7 +14,7 @@ module Decidim
14
14
  def initialize(user, _context)
15
15
  @user = user
16
16
 
17
- return unless user && !user.role?(:admin)
17
+ return unless user && !user.role?(:admin) && !user.role?(:collaborator)
18
18
 
19
19
  can :read, :admin_dashboard do
20
20
  participatory_processes.any?
@@ -8,7 +8,7 @@ module Decidim
8
8
  belongs_to :participatory_process, foreign_key: "decidim_participatory_process_id", class_name: Decidim::ParticipatoryProcess
9
9
 
10
10
  ROLES = %w(admin).freeze
11
- validates :role, inclusion: { in: ROLES }
11
+ validates :role, inclusion: { in: ROLES }, uniqueness: { scope: [:user, :participatory_process] }
12
12
  end
13
13
  end
14
14
  end
@@ -22,7 +22,7 @@ module Decidim
22
22
  #
23
23
  # Returns an ActiveRecord::Relation.
24
24
  def query
25
- return user.organization.participatory_processes if user.role?(:admin)
25
+ return user.organization.participatory_processes if user.role?(:admin) || user.role?(:collaborator)
26
26
 
27
27
  ParticipatoryProcess.where(id: process_ids)
28
28
  end
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title" %></h3>
2
2
 
3
- <%= form_for(@form, url: url_for([@attachment.attached_to, @attachment])) do |f| %>
3
+ <%= decidim_form_for(@form, url: url_for([@attachment.attached_to, @attachment])) do |f| %>
4
4
  <%= render partial: 'decidim/admin/attachments/form', object: f %>
5
5
 
6
6
  <div class="actions">
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title" %></h3>
2
2
 
3
- <%= form_for(@form, url: [attached_to, @form]) do |f| %>
3
+ <%= decidim_form_for(@form, url: [attached_to, @form]) do |f| %>
4
4
  <%= render partial: 'decidim/admin/attachments/form', object: f %>
5
5
 
6
6
  <div class="actions">
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title" %></h3>
2
2
 
3
- <%= form_for(@form, url: participatory_process_category_path(@category.participatory_process, @category)) do |f| %>
3
+ <%= decidim_form_for(@form, url: participatory_process_category_path(@category.participatory_process, @category)) do |f| %>
4
4
  <%= render partial: 'form', object: f %>
5
5
 
6
6
  <div class="actions">
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title" %></h3>
2
2
 
3
- <%= form_for(@form, url: participatory_process_categories_path(participatory_process)) do |f| %>
3
+ <%= decidim_form_for(@form, url: participatory_process_categories_path(participatory_process)) do |f| %>
4
4
  <%= render partial: 'form', object: f %>
5
5
 
6
6
  <div class="actions">
@@ -1,3 +1,3 @@
1
- <% content_for :title do %>
1
+ <% provide :title do %>
2
2
  <h2><%= t("decidim.admin.titles.dashboard") %></h2>
3
3
  <% end %>
@@ -0,0 +1,27 @@
1
+ <section id="features">
2
+ <div class="row">
3
+ <div class="columns">
4
+ <h3><%= t(".title") %></h3>
5
+ </div>
6
+ </div>
7
+
8
+ <%= form_for @permissions_form, url: url_for(action: :update), method: "put" do |form| %>
9
+ <%= form.fields_for :permissions, form.object do |form| %>
10
+ <% form.object.permissions.each do |action, permission| %>
11
+ <%= form.fields_for(action, permission) do |form| %>
12
+ <fieldset class="card <%=action %>-permission">
13
+ <div class="card-divider"><%= t("#{feature.manifest.name}.actions.#{action}", scope: "decidim.features") %></div>
14
+ <div class="card-section">
15
+ <%= form.select :authorization_handler_name,
16
+ authorizations.map { |authorization|
17
+ [t("#{authorization}.name", scope: "decidim.authorization_handlers"), authorization]
18
+ }, include_blank: t(".everyone") %>
19
+ <%= form.text_field :options %>
20
+ </div>
21
+ </fieldset>
22
+ <% end %>
23
+ <% end %>
24
+ <% end %>
25
+ <%= form.submit t(".submit") %>
26
+ <% end %>
27
+ </section>
@@ -16,8 +16,18 @@
16
16
  <% end %>
17
17
  <% end %>
18
18
 
19
+ <% if can?(:update, feature) %>
20
+ <% if feature.published? %>
21
+ <%= link_to t("actions.unpublish", scope: "decidim.admin"), url_for(action: :unpublish, id: feature, controller: "features"), class: "small button", method: :put %>
22
+ <% else %>
23
+ <%= link_to t("actions.publish", scope: "decidim.admin"), url_for(action: :publish, id: feature, controller: "features"), class: "small button", method: :put %>
24
+ <% end %>
25
+ <% end %>
26
+
19
27
  <%= link_to t("actions.configure", scope: "decidim.admin"), url_for(action: :edit, id: feature, controller: "features"), class: "small button" if can? :update, feature %>
20
28
 
29
+ <%= link_to t("actions.permissions", scope: "decidim.admin"), edit_participatory_process_feature_permissions_path(feature_id: feature), class: "small button" if can? :update, feature %>
30
+
21
31
  <%= link_to t("actions.destroy", scope: "decidim.admin"), url_for(action: :destroy, id: feature, controller: "features"), method: :delete, class: "small alert button", data: { confirm: t("actions.confirm_destroy", scope: "decidim.admin") } if can? :destroy, feature %>
22
32
  </td>
23
33
  </tr>
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title", name: t("#{@feature.manifest.name}.name", scope: "decidim.features") %></h3>
2
2
 
3
- <%= form_for([participatory_process, @form], url: participatory_process_feature_path(participatory_process, @feature), method: "put") do |form| %>
3
+ <%= decidim_form_for([participatory_process, @form], url: participatory_process_feature_path(participatory_process, @feature), method: "put") do |form| %>
4
4
  <%= render partial: 'form', object: form, locals: { feature: @feature } %>
5
5
 
6
6
  <div class="actions">
@@ -1,6 +1,6 @@
1
1
  <h3><%= t ".title", name: t("#{manifest.name}.name", scope: "decidim.features") %></h3>
2
2
 
3
- <%= form_for([participatory_process, @form], url: participatory_process_features_path(type: params[:type]), method: "post") do |form| %>
3
+ <%= decidim_form_for([participatory_process, @form], url: participatory_process_features_path(type: params[:type]), method: "post") do |form| %>
4
4
  <%= render partial: 'form', object: form, locals: { feature: @feature } %>
5
5
 
6
6
  <div class="actions">
@@ -0,0 +1,5 @@
1
+ <div class="field">
2
+ <%= form.translated :text_field, :subject %>
3
+ <%= form.translated :editor, :body %>
4
+ <%= t('.interpolations_hint') %>
5
+ </div>
@@ -0,0 +1,11 @@
1
+ <% provide :title do %>
2
+ <h2><%= t ".title" %></h2>
3
+ <% end %>
4
+
5
+ <%= decidim_form_for(@form) do |f| %>
6
+ <%= render partial: 'form', object: f %>
7
+
8
+ <div class="actions">
9
+ <%= f.submit t(".save_and_preview") %>
10
+ </div>
11
+ <% end %>