decidim-cdtb 0.6.0 → 0.6.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30a633cfd37b8c99f0aa405a37d8140ee9d5d6ae06a393cfe29513fd91d425bd
4
- data.tar.gz: da2c0fb5f6fdb4e8086e6d24910825bf7a331884446e27c5932e50fb777759b6
3
+ metadata.gz: 4c986754e258f5c89f059019e2eff8a989cb15972c8958a0aef8519246a1c974
4
+ data.tar.gz: a930aa3920de4ffaa6549d86831dcc9f328b8e4be9eab69a3d0e99a4367ba7ad
5
5
  SHA512:
6
- metadata.gz: 16ad1987a516def98adb792653df860c6d6a881b9ae38e3c5578725cc3e9674ddacc96fb1bf1c179e9f2673141f6864f5fd5000759cb6c19890f26f95d7b5b8c
7
- data.tar.gz: 84d66913b5ab49b424d47a29ff46b3897d2acd2b7784c5ed42262578dd55042444f847de0af5931b977bd5574e1c7d47f102b0437077acca50f4a799d7cd0d43
6
+ metadata.gz: 9ae0c57f0806d1d074969b6a4e6f5780d4cac4a6843d0696cc65c1235025e604eb5e6a1c866260a99fd15099b019aee936932888c0cdf0de345622b5a32d684d
7
+ data.tar.gz: 187bcef080e8308048ee7250a8b7b226dc21ff644e875ac254a33164df150338a491d000e79b73058f04aea8bf03d639a1ce76707df340679a0af05610215122
data/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
- ## [Unreleased]
1
+ ## [0.6.2] - 2026-09-10 (minor)
2
+
3
+ - Prevent `cdtb:users:remove` from enqueueing the admin block notification email when blocking users while preserving auditability and moderation records.
4
+
5
+ ## [0.6.1] - 2026-08-27 (patch)
6
+
7
+ - Fix `ArgumentError` in Users::Remover task: `Decidim::CreateReport.call` only takes `(form, reportable)`, drop the extra `reporter_user` argument.
8
+ - Skip orphaned comments (with no `participatory_space`, e.g. their commentable has been removed) when reporting/hiding them in Users::Remover task, instead of aborting the whole task.
9
+ - Add `cdtb:comments:remove_orphaned` task to remove orphaned comments (and their votes, search index entries and moderations).
2
10
 
3
11
  ## [0.6.0] - 2026-08-25 (minor)
4
12
 
data/Gemfile.lock CHANGED
@@ -1,6 +1,6 @@
1
1
  GIT
2
2
  remote: https://github.com/CodiTramuntana/decidim.git
3
- revision: 6c2e53561a9700ad76d0da64f023e6c0690f41ad
3
+ revision: 67e68f735337b9c7fb98de058362e3b457d24c7a
4
4
  branch: release/0.29-stable
5
5
  specs:
6
6
  decidim (0.29.7)
@@ -141,7 +141,7 @@ GIT
141
141
  PATH
142
142
  remote: .
143
143
  specs:
144
- decidim-cdtb (0.6.0)
144
+ decidim-cdtb (0.6.2)
145
145
  decidim (>= 0.29.0)
146
146
  rails (>= 6)
147
147
  ruby-progressbar
@@ -660,7 +660,7 @@ GEM
660
660
  io-console (~> 0.5)
661
661
  request_store (1.5.1)
662
662
  rack (>= 1.4)
663
- responders (3.2.0)
663
+ responders (3.2.1)
664
664
  actionpack (>= 7.0)
665
665
  railties (>= 7.0)
666
666
  rexml (3.4.4)
data/README.md CHANGED
@@ -164,6 +164,22 @@ To execute the task run:
164
164
  bundle exec rake cdtb:users:remove[spam_users.csv,reporter_user@example.org]
165
165
  ```
166
166
 
167
+ ### Comments
168
+
169
+ Tasks related with comments.
170
+
171
+ #### Remove orphaned comments
172
+
173
+ Removes `Decidim::Comments::Comment`'s whose participatory space can no longer be resolved: this happens when their root commentable (e.g. a proposal or a meeting) and/or its participatory space has been removed without cascading its comments. Trying to report or hide these comments raises `ActiveRecord::RecordInvalid` (a `Decidim::Moderation` requires a `participatory_space`), which is the same condition `cdtb:users:remove` skips.
174
+
175
+ The task also removes the comment's votes, search index entries and any moderation/report referencing it.
176
+
177
+ To execute the task run:
178
+
179
+ ```
180
+ bin/rake cdtb:comments:remove_orphaned
181
+ ```
182
+
167
183
  ### Participatory Spaces
168
184
 
169
185
  #### Add content blocks
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ module Comments
6
+ # Removes Decidim::Comments::Comment's that are orphaned: their
7
+ # participatory space can no longer be resolved, be it because their
8
+ # root commentable (e.g. a proposal or a meeting) was removed without
9
+ # cascading its comments, or because the participatory space itself
10
+ # was removed.
11
+ #
12
+ # This is the same condition Decidim::Cdtb::Users::Remover skips when
13
+ # reporting/hiding comments, since Decidim::CreateReport requires a
14
+ # participatory_space to create the Moderation.
15
+ #
16
+ # Rows are deleted with `delete_all` (no callbacks, no validations) on
17
+ # purpose: Decidim::Searchable#remove_from_index blows up for these
18
+ # comments, since it ends up calling `comment.organization.id` and
19
+ # `organization` is nil for an orphaned comment.
20
+ class OrphanedRemover < ::Decidim::Cdtb::Task
21
+ def initialize
22
+ @root_commentable_orphaned = {}
23
+ progress_bar = { title: "Decidim::Comments::Comment" }
24
+ super("REMOVE ORPHANED COMMENTS", progress_bar:)
25
+ end
26
+
27
+ def prepare_execution(_ctx)
28
+ @num_comments = query.count
29
+ log_task_info("Checking #{@num_comments} comments...")
30
+ end
31
+
32
+ def total_items
33
+ @num_comments
34
+ end
35
+
36
+ def do_execution(context)
37
+ progress_bar = context[:progress_bar]
38
+
39
+ query.find_each do |comment|
40
+ remove_comment(comment) if orphaned?(comment)
41
+ progress_bar.increment
42
+ end
43
+ end
44
+
45
+ def end_execution(_ctx)
46
+ log_task_step("#{@num_applied} orphaned comments removed")
47
+ end
48
+
49
+ private
50
+
51
+ # Comments that share the same root_commentable share the same fate,
52
+ # so the check is cached by that key to avoid resolving it once per
53
+ # comment in a heavily-commented orphaned thread. `find_each` always
54
+ # batches by primary key, so no explicit ordering helps that cache hit.
55
+ def query
56
+ Decidim::Comments::Comment.all
57
+ end
58
+
59
+ def orphaned?(comment)
60
+ key = [comment.decidim_root_commentable_type, comment.decidim_root_commentable_id]
61
+ @root_commentable_orphaned.fetch(key) { @root_commentable_orphaned[key] = comment.participatory_space.blank? }
62
+ end
63
+
64
+ def remove_comment(comment)
65
+ remove_moderations(comment)
66
+ Decidim::Comments::CommentVote.where(decidim_comment_id: comment.id).delete_all
67
+ Decidim::SearchableResource.where(resource_type: "Decidim::Comments::Comment", resource_id: comment.id).delete_all
68
+ Decidim::Comments::Comment.where(id: comment.id).delete_all
69
+
70
+ @num_applied += 1
71
+ puts "OK: Comment #{comment.id} removed (orphaned)"
72
+ end
73
+
74
+ def remove_moderations(comment)
75
+ moderation_ids = Decidim::Moderation.where(
76
+ decidim_reportable_type: "Decidim::Comments::Comment",
77
+ decidim_reportable_id: comment.id
78
+ ).pluck(:id)
79
+ return if moderation_ids.empty?
80
+
81
+ Decidim::Report.where(decidim_moderation_id: moderation_ids).delete_all
82
+ Decidim::Moderation.where(id: moderation_ids).delete_all
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "decidim/cdtb/tasks_utils"
4
4
  require "decidim/cdtb/task"
5
+ require "decidim/cdtb/comments/orphaned_remover"
5
6
  require "decidim/cdtb/fixes/nickname_fixer"
6
7
  require "decidim/cdtb/fixes/you_tube_embeds_fixer"
7
8
  require "decidim/cdtb/users/remover"
@@ -67,34 +67,75 @@ module Decidim
67
67
 
68
68
  def manage_comments(comments, user, reporter_user)
69
69
  comments.find_each do |comment|
70
+ next unless orphaned_comment_skip_check(comment)
71
+
70
72
  report_comment(comment, user, reporter_user) unless comment.reported?
71
73
  hide_comment(comment, user, reporter_user) unless comment.hidden?
72
74
  end
73
75
  end
74
76
 
77
+ # Comments whose commentable (or its participatory space) has been removed
78
+ # have no `participatory_space`, which makes `Decidim::CreateReport` blow up
79
+ # (Moderation requires a participatory_space) and would abort the whole batch.
80
+ def orphaned_comment_skip_check(comment)
81
+ return true if comment.participatory_space.present?
82
+
83
+ puts "SKIP: Comment #{comment.id} of User #{comment.decidim_author_id} has no participatory space (orphaned)"
84
+ false
85
+ end
86
+
75
87
  def block_user(user, reporter_user)
76
- params = {
77
- user_id: user.id,
78
- justification: "Confirmed spam suspicious"
79
- }
88
+ justification = "Confirmed spam suspicious"
80
89
 
81
- form = Decidim::Admin::BlockUserForm.from_params(params).with_context(
82
- {
83
- current_organization: user.organization,
84
- current_user: reporter_user
85
- }
86
- )
90
+ return skip_blocked_user(user) if user.blocked?
87
91
 
88
- Decidim::Admin::BlockUser.call(form) do
89
- on(:ok) do
90
- puts "OK: User #{user.id} blocked"
91
- return true
92
- end
92
+ user_block = create_user_block(user, reporter_user, justification)
93
+ apply_block_to_user(user, user_block, reporter_user, justification)
93
94
 
94
- on(:invalid) do
95
- puts "ERROR: User #{user.id} not blocked"
96
- return false
97
- end
95
+ puts "OK: User #{user.id} blocked"
96
+ true
97
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
98
+ puts "ERROR: User #{user.id} not blocked (#{e.message})"
99
+ false
100
+ end
101
+
102
+ def skip_blocked_user(user)
103
+ puts "SKIP: User #{user.id} already blocked"
104
+ false
105
+ end
106
+
107
+ def create_user_block(user, reporter_user, justification)
108
+ Decidim::UserModeration.find_or_create_by!(user:)
109
+
110
+ Decidim::UserBlock.create!(
111
+ justification:,
112
+ user:,
113
+ blocking_user: reporter_user
114
+ )
115
+ end
116
+
117
+ def apply_block_to_user(user, user_block, reporter_user, justification)
118
+ Decidim.traceability.perform_action!(
119
+ "block",
120
+ user,
121
+ reporter_user,
122
+ extra: {
123
+ reportable_type: user.class.name,
124
+ current_justification: justification
125
+ },
126
+ resource: {
127
+ title: user.name
128
+ }
129
+ ) do
130
+ user.assign_attributes(
131
+ blocked: true,
132
+ blocked_at: Time.current,
133
+ block_id: user_block.id,
134
+ extended_data: user.extended_data.to_h.merge("user_name" => user.name),
135
+ name: "Blocked user",
136
+ notifications_sending_frequency: "none"
137
+ )
138
+ user.save!
98
139
  end
99
140
  end
100
141
 
@@ -104,9 +145,11 @@ module Decidim
104
145
  }
105
146
 
106
147
  form = Decidim::DeleteAccountForm.from_params(params).with_context(current_user: user)
148
+ removed_users = 0
107
149
 
108
150
  Decidim::DestroyAccount.call(form) do
109
151
  on(:ok) do
152
+ removed_users += 1
110
153
  puts "OK: User #{user.id} removed"
111
154
  end
112
155
 
@@ -114,18 +157,19 @@ module Decidim
114
157
  puts "ERROR: User #{user.id} not removed"
115
158
  end
116
159
  end
160
+
161
+ @num_applied += removed_users
117
162
  end
118
163
 
119
164
  def report_comment(comment, user, reporter_user)
120
- params = {
121
- reason: "spam",
122
- details: "Spam message"
123
- }
124
-
125
- form = Decidim::ReportForm.from_params(params).with_context(context_for_report(user, comment, reporter_user))
165
+ form = report_form_for(comment, user, reporter_user)
126
166
  reportable = GlobalID::Locator.locate_signed(comment.to_sgid.to_s)
127
167
 
128
- Decidim::CreateReport.call(form, reportable, reporter_user) do
168
+ create_report(form, reportable, comment, user)
169
+ end
170
+
171
+ def create_report(form, reportable, comment, user)
172
+ Decidim::CreateReport.call(form, reportable) do
129
173
  on(:ok) do
130
174
  puts "OK: Comment #{comment.id} of User #{user.id} reported"
131
175
  end
@@ -134,6 +178,17 @@ module Decidim
134
178
  puts "ERROR: Comment #{comment.id} of User #{user.id} not reported"
135
179
  end
136
180
  end
181
+ rescue ActiveRecord::RecordInvalid => e
182
+ puts "ERROR: Comment #{comment.id} of User #{user.id} not reported (#{e.message})"
183
+ end
184
+
185
+ def report_form_for(comment, user, reporter_user)
186
+ params = {
187
+ reason: "spam",
188
+ details: "Spam message"
189
+ }
190
+
191
+ Decidim::ReportForm.from_params(params).with_context(context_for_report(user, comment, reporter_user))
137
192
  end
138
193
 
139
194
  def hide_comment(comment, user, reporter_user)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Decidim
4
4
  module Cdtb
5
- VERSION = "0.6.0"
5
+ VERSION = "0.6.2"
6
6
  DECIDIM_MIN_VERSION = ">= 0.29.0"
7
7
  end
8
8
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/cdtb/tasks"
4
+
5
+ namespace :cdtb do
6
+ namespace :comments do
7
+ desc <<~EODESC
8
+ Remove orphaned Decidim::Comments::Comment's: comments whose participatory
9
+ space can no longer be resolved (their root commentable and/or their
10
+ participatory space has been removed).
11
+ EODESC
12
+ task remove_orphaned: [:environment] do
13
+ task = Decidim::Cdtb::Comments::OrphanedRemover.new
14
+ task.execute!
15
+ end
16
+ end
17
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decidim-cdtb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Valls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-26 00:00:00.000000000 Z
11
+ date: 2026-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: decidim
@@ -91,6 +91,7 @@ files:
91
91
  - decidim-cdtb.gemspec
92
92
  - lib/cdtb/ip_parser.rb
93
93
  - lib/decidim/cdtb.rb
94
+ - lib/decidim/cdtb/comments/orphaned_remover.rb
94
95
  - lib/decidim/cdtb/engine.rb
95
96
  - lib/decidim/cdtb/fixes/nickname_fixer.rb
96
97
  - lib/decidim/cdtb/fixes/you_tube_embeds_fixer.rb
@@ -117,6 +118,7 @@ files:
117
118
  - lib/generators/cdtb/github_actions/templates/zeitwerk.yml
118
119
  - lib/tasks/anonymize.rake
119
120
  - lib/tasks/census.rake
121
+ - lib/tasks/comments.rake
120
122
  - lib/tasks/fix_youtube_embeds.rake
121
123
  - lib/tasks/logs.rake
122
124
  - lib/tasks/multitenants.rake