decidim-centers 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ });