decidim-centers 0.1.1

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +144 -0
  4. data/Rakefile +40 -0
  5. data/app/commands/concerns/decidim/centers/create_omniauth_registration_override.rb +42 -0
  6. data/app/commands/concerns/decidim/centers/create_registration_override.rb +32 -0
  7. data/app/commands/concerns/decidim/centers/publish_center_update_event.rb +20 -0
  8. data/app/commands/concerns/decidim/centers/update_account_override.rb +35 -0
  9. data/app/commands/decidim/centers/admin/create_center.rb +50 -0
  10. data/app/commands/decidim/centers/admin/destroy_center.rb +47 -0
  11. data/app/commands/decidim/centers/admin/update_center.rb +47 -0
  12. data/app/commands/decidim/centers/create_or_update_center_user.rb +44 -0
  13. data/app/commands/decidim/centers/create_or_update_scope_user.rb +44 -0
  14. data/app/controllers/concerns/decidim/centers/admin/needs_select2_snippets.rb +35 -0
  15. data/app/controllers/concerns/decidim/centers/devise/omniauth_registrations_controller_override.rb +21 -0
  16. data/app/controllers/concerns/decidim/centers/devise/sessions_controller_override.rb +21 -0
  17. data/app/controllers/concerns/decidim/centers/with_scopes_helper.rb +15 -0
  18. data/app/controllers/decidim/centers/admin/application_controller.rb +24 -0
  19. data/app/controllers/decidim/centers/admin/centers_controller.rb +106 -0
  20. data/app/controllers/decidim/centers/admin/scopes_controller.rb +40 -0
  21. data/app/forms/concerns/decidim/centers/account_form_override.rb +36 -0
  22. data/app/forms/decidim/centers/admin/center_form.rb +18 -0
  23. data/app/forms/decidim/centers/verifications/center.rb +33 -0
  24. data/app/helpers/decidim/centers/application_helper.rb +17 -0
  25. data/app/jobs/decidim/centers/auto_verification_job.rb +46 -0
  26. data/app/jobs/decidim/centers/sync_center_user_job.rb +29 -0
  27. data/app/jobs/decidim/centers/sync_scope_user_job.rb +29 -0
  28. data/app/models/concerns/decidim/centers/unique_by_user.rb +19 -0
  29. data/app/models/concerns/decidim/centers/user_override.rb +33 -0
  30. data/app/models/decidim/centers/application_record.rb +10 -0
  31. data/app/models/decidim/centers/center.rb +25 -0
  32. data/app/models/decidim/centers/center_user.rb +28 -0
  33. data/app/models/decidim/centers/scope_user.rb +28 -0
  34. data/app/overrides/decidim/account/show/centers.html.erb.deface +3 -0
  35. data/app/overrides/decidim/devise/omniauth_registrations/new/centers.html.erb.deface +3 -0
  36. data/app/overrides/decidim/devise/registrations/new/centers.html.erb.deface +3 -0
  37. data/app/packs/entrypoints/decidim_centers_admin_select2.js +2 -0
  38. data/app/packs/src/decidim/centers/admin/resource_permissions_select2.js +68 -0
  39. data/app/packs/stylesheets/vendor/select2_foundation_theme.scss +345 -0
  40. data/app/permissions/decidim/centers/admin/permissions.rb +19 -0
  41. data/app/presenters/decidim/centers/admin_log/center_presenter.rb +29 -0
  42. data/app/views/decidim/centers/_profile_form.html.erb +5 -0
  43. data/app/views/decidim/centers/_registration_form.html.erb +11 -0
  44. data/app/views/decidim/centers/admin/centers/_form.html.erb +10 -0
  45. data/app/views/decidim/centers/admin/centers/edit.html.erb +8 -0
  46. data/app/views/decidim/centers/admin/centers/index.html.erb +45 -0
  47. data/app/views/decidim/centers/admin/centers/new.html.erb +8 -0
  48. data/config/assets.rb +8 -0
  49. data/config/i18n-tasks.yml +11 -0
  50. data/config/locales/en.yml +51 -0
  51. data/db/migrate/20231129114029_create_decidim_centers_centers.rb +13 -0
  52. data/db/migrate/20231130125631_create_decidim_centers_center_users.rb +12 -0
  53. data/db/migrate/20231205153627_create_decidim_centers_scope_users.rb +12 -0
  54. data/lib/decidim/centers/admin.rb +10 -0
  55. data/lib/decidim/centers/admin_engine.rb +40 -0
  56. data/lib/decidim/centers/engine.rb +79 -0
  57. data/lib/decidim/centers/test/factories.rb +25 -0
  58. data/lib/decidim/centers/test/shared_contexts.rb +22 -0
  59. data/lib/decidim/centers/verifications/center_action_authorizer.rb +49 -0
  60. data/lib/decidim/centers/verifications.rb +11 -0
  61. data/lib/decidim/centers/version.rb +10 -0
  62. data/lib/decidim/centers.rb +19 -0
  63. metadata +138 -0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ module Admin
6
+ # This controller allows the create or update a center.
7
+ class CentersController < ApplicationController
8
+ include TranslatableAttributes
9
+
10
+ helper_method :centers, :center
11
+
12
+ def index
13
+ enforce_permission_to :index, :center
14
+ respond_to do |format|
15
+ format.html
16
+ format.json do
17
+ render json: json_centers
18
+ end
19
+ end
20
+ end
21
+
22
+ def new
23
+ enforce_permission_to :create, :center
24
+ @form = form(CenterForm).instance
25
+ end
26
+
27
+ def create
28
+ enforce_permission_to :create, :center
29
+ @form = form(CenterForm).from_params(params)
30
+
31
+ CreateCenter.call(@form, current_user) do
32
+ on(:ok) do
33
+ flash[:notice] = I18n.t("centers.create.success", scope: "decidim.centers.admin")
34
+ redirect_to centers_path
35
+ end
36
+
37
+ on(:invalid) do
38
+ flash.now[:alert] = I18n.t("centers.create.invalid", scope: "decidim.centers.admin")
39
+ render action: "new"
40
+ end
41
+ end
42
+ end
43
+
44
+ def edit
45
+ enforce_permission_to :update, :center, center: center
46
+ @form = form(CenterForm).from_model(center)
47
+ end
48
+
49
+ def update
50
+ enforce_permission_to :update, :center, center: center
51
+ @form = form(CenterForm).from_params(params)
52
+
53
+ UpdateCenter.call(@form, center, current_user) do
54
+ on(:ok) do
55
+ flash[:notice] = I18n.t("centers.update.success", scope: "decidim.centers.admin")
56
+ redirect_to centers_path
57
+ end
58
+
59
+ on(:invalid) do
60
+ flash.now[:alert] = I18n.t("centers.update.invalid", scope: "decidim.centers.admin")
61
+ render action: "edit"
62
+ end
63
+ end
64
+ end
65
+
66
+ def destroy
67
+ enforce_permission_to :destroy, :center, center: center
68
+
69
+ DestroyCenter.call(center, current_user) do
70
+ on(:ok) do
71
+ flash[:notice] = I18n.t("centers.destroy.success", scope: "decidim.centers.admin")
72
+ end
73
+ end
74
+
75
+ redirect_to centers_path
76
+ end
77
+
78
+ private
79
+
80
+ def json_centers
81
+ query = filtered_centers
82
+ query = query.where(id: params[:ids]) if params[:ids]
83
+ query = query.where("title->>? ilike ?", I18n.locale, "%#{params[:q]}%") if params[:q]
84
+ query.map do |item|
85
+ {
86
+ id: item.id,
87
+ text: translated_attribute(item.title)
88
+ }
89
+ end
90
+ end
91
+
92
+ def center
93
+ @center ||= filtered_centers.find(params[:id])
94
+ end
95
+
96
+ def centers
97
+ @centers ||= filtered_centers.page(params[:page]).per(15)
98
+ end
99
+
100
+ def filtered_centers
101
+ Center.where(organization: current_organization).not_deleted
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ module Admin
6
+ class ScopesController < ApplicationController
7
+ include TranslatableAttributes
8
+
9
+ helper_method :scopes
10
+
11
+ def index
12
+ enforce_permission_to :read, :scope
13
+ respond_to do |format|
14
+ format.json do
15
+ render json: json_scopes
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def json_scopes
23
+ query = scopes
24
+ query = query.where(id: params[:ids]) if params[:ids]
25
+ query = query.where("name->>? ilike ?", I18n.locale, "%#{params[:q]}%") if params[:q]
26
+ query.map do |item|
27
+ {
28
+ id: item.id,
29
+ text: translated_attribute(item.name)
30
+ }
31
+ end
32
+ end
33
+
34
+ def scopes
35
+ @scopes ||= Scope.where(organization: current_organization)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Centers
7
+ module AccountFormOverride
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ alias_method :original_map_model, :map_model
12
+
13
+ include Decidim::Centers::ApplicationHelper
14
+
15
+ attribute :center_id, Integer
16
+ attribute :scope_id, Integer
17
+
18
+ validates :center_id, presence: true
19
+ validates :scope_id, presence: true, if: :scope_id?
20
+
21
+ def map_model(model)
22
+ original_map_model(model)
23
+
24
+ self.center_id = model.center.try(:id)
25
+ self.scope_id = model.scope.try(:id)
26
+ end
27
+
28
+ private
29
+
30
+ def scope_id?
31
+ Decidim::Centers.scopes_enabled
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ module Admin
6
+ # This class holds a Form to update centers from Decidim's admin panel.
7
+ class CenterForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ translatable_attribute :title, String
11
+
12
+ validates :title, translatable_presence: true
13
+
14
+ alias organization current_organization
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Decidim
6
+ module Centers
7
+ module Verifications
8
+ class Center < Decidim::AuthorizationHandler
9
+ validate :center_present
10
+ validate :scope_present
11
+
12
+ def metadata
13
+ super.merge(
14
+ centers: user.centers.pluck(:id),
15
+ scopes: user.scopes.pluck(:id)
16
+ )
17
+ end
18
+
19
+ protected
20
+
21
+ def center_present
22
+ errors.add(:user, I18n.t("decidim.centers.authorizations.new.error")) unless user.centers.any?
23
+ end
24
+
25
+ def scope_present
26
+ return unless Decidim::Centers.scopes_enabled
27
+
28
+ errors.add(:user, I18n.t("decidim.centers.authorizations.new.error")) unless user.scopes.any?
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ # Custom helpers, scoped to the centers engine.
6
+ #
7
+ module ApplicationHelper
8
+ include TranslatableAttributes
9
+
10
+ def center_options_for_select
11
+ Decidim::Centers::Center.where(organization: current_organization).map do |center|
12
+ [center.id, translated_attribute(center.title)]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ class AutoVerificationJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(user_id)
9
+ @user = Decidim::User.find(user_id)
10
+ @user.centers.any? || @user.scopes.any? ? create_auth : remove_auth
11
+ rescue ActiveRecord::RecordNotFound => _e
12
+ Rails.logger.error "AutoVerificationJob: ERROR: user not found #{user_id}"
13
+ end
14
+
15
+ private
16
+
17
+ def create_auth
18
+ return unless (handler = Decidim::AuthorizationHandler.handler_for("center", user: @user))
19
+
20
+ Decidim::Verifications::AuthorizeUser.call(handler, @user.organization) do
21
+ on(:ok) do
22
+ Rails.logger.info "AutoVerificationJob: Success: created for user #{handler.user.id}"
23
+ end
24
+
25
+ on(:invalid) do
26
+ Rails.logger.error "AutoVerificationJob: ERROR: not created for user #{handler.user&.id}"
27
+ end
28
+ end
29
+ end
30
+
31
+ def remove_auth
32
+ Decidim::Authorization.where(user: @user, name: "center").each do |auth|
33
+ Decidim::Verifications::DestroyUserAuthorization.call(auth) do
34
+ on(:ok) do
35
+ Rails.logger.info "AutoVerificationJob: Success: removed for user #{auth.user.id}"
36
+ end
37
+
38
+ on(:invalid) do
39
+ Rails.logger.error "AutoVerificationJob: ERROR: not removed for user #{auth.user&.id}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ class SyncCenterUserJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(data)
9
+ @user = Decidim::User.find(data[:user_id])
10
+ @center = Decidim::Centers::Center.find(data[:center_id])
11
+ create_or_update_center_user
12
+ end
13
+
14
+ private
15
+
16
+ def create_or_update_center_user
17
+ Decidim::Centers::CreateOrUpdateCenterUser.call(@center, @user) do
18
+ on(:ok) do
19
+ Rails.logger.info "SyncCenterUserJob: Success: updated for user #{@user.id}"
20
+ end
21
+
22
+ on(:invalid) do
23
+ Rails.logger.error "SyncCenterUserJob: ERROR: not updated for user #{@user.id}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ class SyncScopeUserJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(data)
9
+ @user = Decidim::User.find(data[:user_id])
10
+ @scope = Decidim::Scope.find(data[:scope_id])
11
+ create_or_update_scope_user
12
+ end
13
+
14
+ private
15
+
16
+ def create_or_update_scope_user
17
+ Decidim::Centers::CreateOrUpdateScopeUser.call(@scope, @user) do
18
+ on(:ok) do
19
+ Rails.logger.info "SyncScopeUserJob: Success: updated for user #{@user.id}"
20
+ end
21
+
22
+ on(:invalid) do
23
+ Rails.logger.error "SyncScopeUserJob: ERROR: not updated for user #{@user.id}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ module UniqueByUser
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ validate :unique_by_user
10
+
11
+ def unique_by_user
12
+ return if self.class.where(user: user).empty?
13
+
14
+ errors.add(:user, :invalid)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ module UserOverride
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :center_users,
10
+ class_name: "Decidim::Centers::CenterUser",
11
+ foreign_key: "decidim_user_id",
12
+ dependent: :destroy
13
+
14
+ has_many :centers, through: :center_users
15
+
16
+ has_many :scope_users,
17
+ class_name: "Decidim::Centers::ScopeUser",
18
+ foreign_key: "decidim_user_id",
19
+ dependent: :destroy
20
+
21
+ has_many :scopes, through: :scope_users
22
+
23
+ def center
24
+ centers.first
25
+ end
26
+
27
+ def scope
28
+ scopes.first
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ # Abstract class from which all models in this engine inherit.
6
+ class ApplicationRecord < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ class Center < Centers::ApplicationRecord
6
+ include Decidim::TranslatableResource
7
+
8
+ translatable_fields :title
9
+
10
+ belongs_to :organization,
11
+ foreign_key: "decidim_organization_id",
12
+ class_name: "Decidim::Organization"
13
+
14
+ scope :not_deleted, -> { where(deleted_at: nil) }
15
+
16
+ def deleted?
17
+ deleted_at.present?
18
+ end
19
+
20
+ def self.log_presenter_class_for(_log)
21
+ Decidim::Centers::AdminLog::CenterPresenter
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ class CenterUser < Centers::ApplicationRecord
6
+ include UniqueByUser
7
+
8
+ belongs_to :center,
9
+ foreign_key: "decidim_centers_center_id",
10
+ class_name: "Decidim::Centers::Center"
11
+
12
+ belongs_to :user,
13
+ foreign_key: "decidim_user_id",
14
+ class_name: "Decidim::User"
15
+
16
+ validate :same_organization
17
+
18
+ private
19
+
20
+ def same_organization
21
+ return if center.try(:organization) == user.try(:organization)
22
+
23
+ errors.add(:center, :invalid)
24
+ errors.add(:user, :invalid)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Centers
5
+ class ScopeUser < Centers::ApplicationRecord
6
+ include UniqueByUser
7
+
8
+ belongs_to :scope,
9
+ foreign_key: "decidim_scope_id",
10
+ class_name: "Decidim::Scope"
11
+
12
+ belongs_to :user,
13
+ foreign_key: "decidim_user_id",
14
+ class_name: "Decidim::User"
15
+
16
+ validate :same_organization
17
+
18
+ private
19
+
20
+ def same_organization
21
+ return if scope.try(:organization) == user.try(:organization)
22
+
23
+ errors.add(:scope, :invalid)
24
+ errors.add(:user, :invalid)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ <!-- insert_after "erb[loud]:contains('email_field')" original "d629550ba83d41a6d1c41553b7adbed311549633" -->
2
+
3
+ <%= render partial: "/decidim/centers/profile_form", locals: { f: f } %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_after "erb[loud]:contains('email_field')" -->
2
+
3
+ <%= render partial: "/decidim/centers/registration_form", locals: { f: f } %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_before "#card__tos" -->
2
+
3
+ <%= render partial: "/decidim/centers/registration_form", locals: { f: f } %>
@@ -0,0 +1,2 @@
1
+ import "src/decidim/centers/admin/resource_permissions_select2.js";
2
+ import "stylesheets/vendor/select2_foundation_theme.scss";
@@ -0,0 +1,68 @@
1
+ $(() => {
2
+
3
+ /**
4
+ * Used to override simple inputs in the resource permissions controller
5
+ * Allows to use more than one center when configuring :centers authorization handler
6
+ * */
7
+ const fields = [
8
+ {
9
+ url: "/admin/centers/centers",
10
+ inputName: "[authorization_handlers_options][center][centers]"
11
+ },
12
+ {
13
+ url: "/admin/centers/scopes",
14
+ inputName: "[authorization_handlers_options][center][scopes]"
15
+ }
16
+ ]
17
+
18
+ const select2InputTags = (queryStr, url) => {
19
+ const $input = $(queryStr)
20
+
21
+ const $select = $(`<select class="${$input.attr("class")}" style="width:100%" multiple="multiple"><select>`);
22
+ if ($input.val() !== "") {
23
+ const values = $input.val().split(",");
24
+ values.forEach((item) => {
25
+ $select.append(`<option value="${item}" selected="selected">${item}</option>`)
26
+ })
27
+ ;
28
+ // load text via ajax
29
+ $.get(url, { ids: values }, (data) => {
30
+ $select.val("");
31
+ $select.contents("option").remove()
32
+ data.forEach((item) => {
33
+ $select.append(new Option(item.text, item.id, true, true));
34
+ });
35
+ $select.trigger("change");
36
+ }, "json");
37
+ }
38
+ $select.insertAfter($input);
39
+ $input.hide();
40
+
41
+ $select.change(() => {
42
+ $input.val($select.val().join(","));
43
+ });
44
+
45
+ return $select;
46
+ };
47
+
48
+ // Groups multiselect permissions
49
+ fields.forEach((field) => {
50
+ $(`input[name$='${field.inputName}']`).each((idx, input) => {
51
+ select2InputTags(input, field.url).select2({
52
+ ajax: {
53
+ url: field.url,
54
+ delay: 100,
55
+ dataType: "json",
56
+ processResults: (data) => {
57
+ return {
58
+ results: data
59
+ }
60
+ }
61
+ },
62
+ width: "100%",
63
+ multiple: true,
64
+ theme: "foundation"
65
+ });
66
+ });
67
+ });
68
+ });