rails-image-post-solution 0.1.0

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +8 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +42 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +264 -0
  7. data/Rakefile +8 -0
  8. data/app/controllers/admin/frozen_posts_controller.rb +79 -0
  9. data/app/controllers/admin/image_reports_controller.rb +81 -0
  10. data/app/controllers/admin/users_controller.rb +100 -0
  11. data/app/controllers/rails_image_post_solution/admin/image_reports_controller.rb +92 -0
  12. data/app/controllers/rails_image_post_solution/image_reports_controller.rb +87 -0
  13. data/app/jobs/rails_image_post_solution/image_moderation_job.rb +97 -0
  14. data/app/models/rails_image_post_solution/image_report.rb +57 -0
  15. data/app/services/rails_image_post_solution/openai_vision_service.rb +128 -0
  16. data/app/views/admin/frozen_posts/index.html.erb +128 -0
  17. data/app/views/admin/image_reports/index.html.erb +94 -0
  18. data/app/views/admin/image_reports/show.html.erb +138 -0
  19. data/app/views/admin/users/index.html.erb +93 -0
  20. data/app/views/admin/users/show.html.erb +198 -0
  21. data/app/views/rails_image_post_solution/admin/image_reports/index.html.erb +100 -0
  22. data/app/views/rails_image_post_solution/admin/image_reports/show.html.erb +154 -0
  23. data/app/views/shared/_image_report_button.html.erb +26 -0
  24. data/app/views/shared/_image_report_modal.html.erb +45 -0
  25. data/config/locales/en.yml +260 -0
  26. data/config/locales/ja.yml +259 -0
  27. data/config/routes.rb +16 -0
  28. data/db/migrate/20250101000001_create_rails_image_post_solution_image_reports.rb +20 -0
  29. data/db/migrate/20250101000002_add_ai_moderation_fields_to_image_reports.rb +12 -0
  30. data/lib/generators/rails_image_post_solution/install/README +27 -0
  31. data/lib/generators/rails_image_post_solution/install/install_generator.rb +47 -0
  32. data/lib/generators/rails_image_post_solution/install/templates/add_ai_moderation_fields.rb.erb +12 -0
  33. data/lib/generators/rails_image_post_solution/install/templates/create_image_reports.rb.erb +19 -0
  34. data/lib/generators/rails_image_post_solution/install/templates/rails_image_post_solution.rb +15 -0
  35. data/lib/rails_image_post_solution/engine.rb +22 -0
  36. data/lib/rails_image_post_solution/version.rb +5 -0
  37. data/lib/rails_image_post_solution.rb +28 -0
  38. metadata +150 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 61ff05d1659ea0e22251e785b60a0a3e9c08f200f63b54a16ddb77edfd046987
4
+ data.tar.gz: b211876b47d97ef9da39dd0197c688bde4951d9c2e100cf20da227e4b7129425
5
+ SHA512:
6
+ metadata.gz: f448802dbaa0cbbeb74555219f1100ec50373a0cea862d8322efb63e653fea5420c3bab1413f9086feee2973f907f225188cbeffc4e6abdb271b494bcd189976
7
+ data.tar.gz: 0bdadbfc8e131fface96b9d3d9269e548687d11c1d65bbd2975b9aecbe3f3b4a08e536207e0136eb6607fe4b042be2b0f69a45cd8a55160c2ff35e10d49fc876
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ # Overwrite or add rules to create your own house style
5
+ #
6
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
+ # Layout/SpaceInsideArrayLiteralBrackets:
8
+ # Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.7
data/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2025-01-17
11
+
12
+ ### Added
13
+ - Initial release of RailsImagePostSolution
14
+ - Image reporting system for Active Storage attachments
15
+ - AI-powered moderation using OpenAI Vision API
16
+ - Detects R18 (adult content)
17
+ - Detects R18G (grotesque/violent content)
18
+ - Detects illegal content
19
+ - Admin dashboard for reviewing image reports
20
+ - View all reports with filtering by status
21
+ - Detailed report view with image preview
22
+ - Confirm or dismiss reports
23
+ - View AI moderation results
24
+ - Auto-freeze functionality for inappropriate content
25
+ - Rails generator for easy installation (`rails generate rails_image_post_solution:install`)
26
+ - Configuration system with initializer
27
+ - i18n support (Japanese and English)
28
+ - Database migrations for image_reports table
29
+ - User reporting API endpoint
30
+ - Comprehensive README with installation and usage instructions
31
+
32
+ ### Features
33
+ - Configurable OpenAI API key
34
+ - Configurable admin check method
35
+ - Optional auto-freeze on content flagged by AI
36
+ - Support for nil user_id (system/AI reports)
37
+ - Unique index to prevent duplicate reports from same user
38
+ - AI confidence scores and category detection
39
+ - View helper methods for easy integration
40
+
41
+ [Unreleased]: https://github.com/dhq-boiler/rails-image-post-solution/compare/v0.1.0...HEAD
42
+ [0.1.0]: https://github.com/dhq-boiler/rails-image-post-solution/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 dhq_boiler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,264 @@
1
+ # RailsImagePostSolution
2
+
3
+ A comprehensive Rails engine for image reporting, AI-powered moderation using OpenAI Vision API, and admin dashboard. Perfect for Rails applications using Active Storage that need to moderate user-generated image content.
4
+
5
+ ## Features
6
+
7
+ - **Image Reporting System**: Allow users to report inappropriate images
8
+ - **AI-Powered Moderation**: Automatic content moderation using OpenAI Vision API
9
+ - Detects R18 (adult content)
10
+ - Detects R18G (grotesque/violent content)
11
+ - Detects illegal content
12
+ - **Admin Dashboard**: Review and manage reported images
13
+ - **Auto-Freeze**: Automatically freeze posts containing flagged content (configurable)
14
+ - **i18n Support**: Japanese and English locales included
15
+ - **Highly Configurable**: Customize behavior to fit your application
16
+
17
+ ## Requirements
18
+
19
+ - Ruby >= 3.2.0
20
+ - Rails >= 7.0
21
+ - Active Storage
22
+ - OpenAI API key (for AI moderation features)
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem "rails-image-post-solution"
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ ```bash
35
+ bundle install
36
+ ```
37
+
38
+ Run the installation generator:
39
+
40
+ ```bash
41
+ rails generate rails_image_post_solution:install
42
+ ```
43
+
44
+ This will:
45
+ 1. Create an initializer at `config/initializers/rails_image_post_solution.rb`
46
+ 2. Copy migration files to your application
47
+ 3. Display installation instructions
48
+
49
+ Run the migrations:
50
+
51
+ ```bash
52
+ rails db:migrate
53
+ ```
54
+
55
+ Mount the engine in your `config/routes.rb`:
56
+
57
+ ```ruby
58
+ Rails.application.routes.draw do
59
+ mount RailsImagePostSolution::Engine => "/moderation"
60
+ # ... your other routes
61
+ end
62
+ ```
63
+
64
+ ## Configuration
65
+
66
+ Edit `config/initializers/rails_image_post_solution.rb`:
67
+
68
+ ```ruby
69
+ RailsImagePostSolution.configure do |config|
70
+ # OpenAI API key (recommended to use environment variable)
71
+ config.openai_api_key = ENV["OPENAI_API_KEY"]
72
+
73
+ # Auto-freeze posts when inappropriate content is detected
74
+ # Default: true
75
+ config.auto_freeze_on_flag = true
76
+
77
+ # Method name to check if current_user is admin
78
+ # Default: :admin?
79
+ config.admin_check_method = :admin?
80
+ end
81
+ ```
82
+
83
+ Set your OpenAI API key as an environment variable:
84
+
85
+ ```bash
86
+ export OPENAI_API_KEY=sk-...
87
+ ```
88
+
89
+ ## Usage
90
+
91
+ ### User Reporting
92
+
93
+ Users can report images via POST request:
94
+
95
+ ```ruby
96
+ # In your view
97
+ <%= button_to "Report", rails_image_post_solution.image_reports_path(attachment_id: @image.id, reason: "Inappropriate content"), method: :post %>
98
+ ```
99
+
100
+ Or via JavaScript:
101
+
102
+ ```javascript
103
+ fetch('/moderation/image_reports', {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
108
+ },
109
+ body: JSON.stringify({
110
+ attachment_id: imageId,
111
+ reason: 'R18 content'
112
+ })
113
+ });
114
+ ```
115
+
116
+ ### Admin Dashboard
117
+
118
+ Admins can access the dashboard at:
119
+
120
+ ```
121
+ /moderation/admin/image_reports
122
+ ```
123
+
124
+ Features:
125
+ - View all reports
126
+ - Filter by status (pending, confirmed, dismissed, reviewed)
127
+ - View detailed information about each report
128
+ - Mark reports as confirmed (inappropriate) or dismissed (safe)
129
+ - View AI moderation results
130
+
131
+ ### AI Moderation
132
+
133
+ AI moderation runs automatically when the first report is submitted for an image. You can also trigger it manually:
134
+
135
+ ```ruby
136
+ RailsImagePostSolution::ImageModerationJob.perform_later(attachment_id)
137
+ ```
138
+
139
+ ### Post Freezing (Optional)
140
+
141
+ To enable automatic post freezing, add a `freeze_post!` method to your models that have images:
142
+
143
+ ```ruby
144
+ class Post < ApplicationRecord
145
+ has_many_attached :images
146
+
147
+ def freeze_post!(type:, reason:)
148
+ update!(
149
+ frozen: true,
150
+ frozen_type: type,
151
+ frozen_reason: reason,
152
+ frozen_at: Time.current
153
+ )
154
+ end
155
+
156
+ def frozen?
157
+ frozen == true
158
+ end
159
+ end
160
+ ```
161
+
162
+ ## Requirements for Host Application
163
+
164
+ Your Rails application must have:
165
+
166
+ 1. **Active Storage** configured
167
+ 2. **User model** with:
168
+ - `current_user` helper method in controllers
169
+ - `admin?` method (or custom method specified in config)
170
+ - Display name method (tries `display_name` or `name`)
171
+
172
+ Example User model:
173
+
174
+ ```ruby
175
+ class User < ApplicationRecord
176
+ def admin?
177
+ role == "admin"
178
+ end
179
+
180
+ def display_name
181
+ name || email
182
+ end
183
+ end
184
+ ```
185
+
186
+ ## Routes
187
+
188
+ The engine provides the following routes:
189
+
190
+ ```
191
+ POST /moderation/image_reports # Create report
192
+ GET /moderation/admin/image_reports # List all reports
193
+ GET /moderation/admin/image_reports/:id # View report details
194
+ PATCH /moderation/admin/image_reports/:id/confirm # Mark as inappropriate
195
+ PATCH /moderation/admin/image_reports/:id/dismiss # Mark as safe
196
+ ```
197
+
198
+ ## Customization
199
+
200
+ ### Custom Admin Check
201
+
202
+ You can customize how admin access is checked:
203
+
204
+ ```ruby
205
+ # config/initializers/rails_image_post_solution.rb
206
+ config.admin_check_method = :moderator? # or any method name
207
+ ```
208
+
209
+ ### Custom Styling
210
+
211
+ Override the engine's views by creating files in your app:
212
+
213
+ ```
214
+ app/views/rails_image_post_solution/admin/image_reports/index.html.erb
215
+ app/views/rails_image_post_solution/admin/image_reports/show.html.erb
216
+ ```
217
+
218
+ ### Custom Moderation Logic
219
+
220
+ You can subclass and override the moderation job:
221
+
222
+ ```ruby
223
+ class CustomImageModerationJob < RailsImagePostSolution::ImageModerationJob
224
+ def perform(attachment_id)
225
+ # Your custom logic
226
+ super
227
+ end
228
+ end
229
+ ```
230
+
231
+ ## Database Schema
232
+
233
+ The gem creates one table: `image_reports`
234
+
235
+ | Column | Type | Description |
236
+ |--------------------------------|----------|--------------------------------------|
237
+ | active_storage_attachment_id | integer | Reference to Active Storage |
238
+ | user_id | integer | Reporter (null for AI reports) |
239
+ | reason | text | Reason for report |
240
+ | status | string | pending/confirmed/dismissed/reviewed |
241
+ | reviewed_by_id | integer | Admin who reviewed |
242
+ | reviewed_at | datetime | When reviewed |
243
+ | ai_flagged | boolean | Flagged by AI |
244
+ | ai_confidence | float | AI confidence score (0.0-1.0) |
245
+ | ai_categories | text | JSON of detected categories |
246
+ | ai_detected_at | datetime | When AI detection ran |
247
+
248
+ ## Development
249
+
250
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
251
+
252
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
253
+
254
+ ## Contributing
255
+
256
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dhq-boiler/rails-image-post-solution.
257
+
258
+ ## License
259
+
260
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
261
+
262
+ ## Author
263
+
264
+ dhq_boiler (dhq_boiler@live.jp)
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[spec]
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class FrozenPostsController < ApplicationController
5
+ before_action :require_login
6
+ before_action :require_admin
7
+
8
+ def index
9
+ @filter = params[:filter] || "all"
10
+
11
+ # Get frozen stages and comments
12
+ stages = Stage.frozen.includes(:user).recent
13
+ comments = MultiplayRecruitmentComment.frozen.includes(:user, :multiplay_recruitment).recent
14
+
15
+ case @filter
16
+ when "temporary"
17
+ stages = stages.temporarily_frozen
18
+ comments = comments.temporarily_frozen
19
+ when "permanent"
20
+ stages = stages.permanently_frozen
21
+ comments = comments.permanently_frozen
22
+ end
23
+
24
+ # Combine into array and sort by frozen_at
25
+ @frozen_posts = (stages.to_a + comments.to_a).sort_by(&:frozen_at).reverse
26
+
27
+ # Statistics
28
+ @stats = {
29
+ total: Stage.frozen.count + MultiplayRecruitmentComment.frozen.count,
30
+ temporary: Stage.temporarily_frozen.count + MultiplayRecruitmentComment.temporarily_frozen.count,
31
+ permanent: Stage.permanently_frozen.count + MultiplayRecruitmentComment.permanently_frozen.count
32
+ }
33
+ end
34
+
35
+ def unfreeze_stage
36
+ stage = Stage.find(params[:id])
37
+ stage.unfreeze!
38
+ redirect_to admin_frozen_posts_path, notice: I18n.t("admin.flash.stage_unfrozen")
39
+ end
40
+
41
+ def unfreeze_comment
42
+ comment = MultiplayRecruitmentComment.find(params[:id])
43
+ comment.unfreeze!
44
+ redirect_to admin_frozen_posts_path, notice: I18n.t("admin.flash.comment_unfrozen")
45
+ end
46
+
47
+ def permanent_freeze_stage
48
+ stage = Stage.find(params[:id])
49
+ stage.freeze_post!(type: :permanent, reason: params[:reason] || I18n.t("admin.flash.permanent_freeze_by_admin"))
50
+ redirect_to admin_frozen_posts_path, notice: I18n.t("admin.flash.stage_permanently_frozen")
51
+ end
52
+
53
+ def permanent_freeze_comment
54
+ comment = MultiplayRecruitmentComment.find(params[:id])
55
+ comment.freeze_post!(type: :permanent, reason: params[:reason] || I18n.t("admin.flash.permanent_freeze_by_admin"))
56
+ redirect_to admin_frozen_posts_path, notice: I18n.t("admin.flash.comment_permanently_frozen")
57
+ end
58
+
59
+ def destroy_stage
60
+ stage = Stage.find(params[:id])
61
+ stage.destroy
62
+ redirect_to admin_frozen_posts_path, notice: I18n.t("admin.flash.stage_deleted")
63
+ end
64
+
65
+ def destroy_comment
66
+ comment = MultiplayRecruitmentComment.find(params[:id])
67
+ comment.destroy
68
+ redirect_to admin_frozen_posts_path, notice: I18n.t("admin.flash.comment_deleted")
69
+ end
70
+
71
+ private
72
+
73
+ def require_admin
74
+ return if current_user.admin?
75
+
76
+ redirect_to root_path, alert: I18n.t("admin.flash.access_denied")
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class ImageReportsController < ApplicationController
5
+ before_action :require_login
6
+ before_action :require_admin
7
+ before_action :set_report, only: %i[show confirm dismiss]
8
+
9
+ def index
10
+ @status_filter = params[:status] || "all"
11
+
12
+ @reports = ImageReport.includes(:user, :active_storage_attachment, :reviewed_by)
13
+ .recent
14
+
15
+ # Filter by status
16
+ case @status_filter
17
+ when "pending"
18
+ @reports = @reports.pending
19
+ when "confirmed"
20
+ @reports = @reports.confirmed
21
+ when "dismissed"
22
+ @reports = @reports.dismissed
23
+ when "reviewed"
24
+ @reports = @reports.reviewed
25
+ end
26
+
27
+ @reports = @reports.limit(100)
28
+
29
+ # Statistics
30
+ @stats = {
31
+ total: ImageReport.count,
32
+ pending: ImageReport.pending.count,
33
+ confirmed: ImageReport.confirmed.count,
34
+ dismissed: ImageReport.dismissed.count,
35
+ reviewed: ImageReport.reviewed.count
36
+ }
37
+ end
38
+
39
+ def show
40
+ @attachment = @report.active_storage_attachment
41
+ @reported_user = @attachment.record.user if @attachment.record.respond_to?(:user)
42
+ @all_reports = ImageReport.where(active_storage_attachment_id: @attachment.id)
43
+ .includes(:user)
44
+ .recent
45
+ end
46
+
47
+ def confirm
48
+ @report.update!(
49
+ status: ImageReport::STATUSES[:confirmed],
50
+ reviewed_by: current_user,
51
+ reviewed_at: Time.current
52
+ )
53
+
54
+ redirect_to admin_image_reports_path, notice: I18n.t("admin.image_reports.flash.report_confirmed")
55
+ end
56
+
57
+ def dismiss
58
+ @report.update!(
59
+ status: ImageReport::STATUSES[:dismissed],
60
+ reviewed_by: current_user,
61
+ reviewed_at: Time.current
62
+ )
63
+
64
+ redirect_to admin_image_reports_path, notice: I18n.t("admin.image_reports.flash.report_dismissed")
65
+ end
66
+
67
+ private
68
+
69
+ def set_report
70
+ @report = ImageReport.find(params[:id])
71
+ rescue ActiveRecord::RecordNotFound
72
+ redirect_to admin_image_reports_path, alert: I18n.t("admin.image_reports.flash.report_not_found")
73
+ end
74
+
75
+ def require_admin
76
+ return if current_user.admin?
77
+
78
+ redirect_to root_path, alert: I18n.t("admin.flash.admin_access_only")
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class UsersController < ApplicationController
5
+ before_action :require_login
6
+ before_action :require_admin
7
+ before_action :set_user, only: %i[show suspend unsuspend ban unban]
8
+
9
+ def index
10
+ @status_filter = params[:status] || "all"
11
+
12
+ @users = User.order(created_at: :desc)
13
+
14
+ # Filter by status
15
+ case @status_filter
16
+ when "active"
17
+ @users = @users.select(&:active?)
18
+ when "suspended"
19
+ @users = @users.select(&:suspended?)
20
+ when "banned"
21
+ @users = @users.select(&:banned?)
22
+ when "admin"
23
+ @users = @users.where(admin: true)
24
+ end
25
+
26
+ @users = @users.first(100)
27
+
28
+ # Statistics
29
+ @stats = {
30
+ total: User.count,
31
+ active: User.all.count(&:active?),
32
+ suspended: User.all.count(&:suspended?),
33
+ banned: User.all.count(&:banned?),
34
+ admin: User.where(admin: true).count
35
+ }
36
+ end
37
+
38
+ def show
39
+ @stages = @user.stages.recent.limit(10)
40
+ @comments = @user.multiplay_recruitment_comments.order(created_at: :desc).limit(10)
41
+ @reports_made = ImageReport.where(user_id: @user.id).order(created_at: :desc).limit(10)
42
+
43
+ # Reports against this user's images
44
+ @reports_received = ImageReport
45
+ .joins(active_storage_attachment: :blob)
46
+ .where(active_storage_attachments: { record_type: %w[Stage MultiplayRecruitmentComment] })
47
+ .where("active_storage_attachments.record_id IN (
48
+ SELECT id FROM stages WHERE user_id = :user_id
49
+ UNION
50
+ SELECT id FROM multiplay_recruitment_comments WHERE user_id = :user_id
51
+ )", user_id: @user.id)
52
+ .order(created_at: :desc)
53
+ .limit(10)
54
+ end
55
+
56
+ def suspend
57
+ duration_days = params[:duration]&.to_i || 7
58
+ reason = params[:reason]
59
+
60
+ @user.suspend!(reason: reason, duration: duration_days.days)
61
+
62
+ redirect_to admin_user_path(@user),
63
+ notice: I18n.t("admin.flash.user_suspended", name: @user.display_name, days: duration_days)
64
+ end
65
+
66
+ def unsuspend
67
+ @user.unsuspend!
68
+
69
+ redirect_to admin_user_path(@user), notice: I18n.t("admin.flash.user_unsuspended", name: @user.display_name)
70
+ end
71
+
72
+ def ban
73
+ reason = params[:reason]
74
+
75
+ @user.ban!(reason: reason)
76
+
77
+ redirect_to admin_user_path(@user), notice: I18n.t("admin.flash.user_banned", name: @user.display_name)
78
+ end
79
+
80
+ def unban
81
+ @user.unban!
82
+
83
+ redirect_to admin_user_path(@user), notice: I18n.t("admin.flash.user_unbanned", name: @user.display_name)
84
+ end
85
+
86
+ private
87
+
88
+ def set_user
89
+ @user = User.find(params[:id])
90
+ rescue ActiveRecord::RecordNotFound
91
+ redirect_to admin_users_path, alert: I18n.t("admin.flash.user_not_found")
92
+ end
93
+
94
+ def require_admin
95
+ return if current_user.admin?
96
+
97
+ redirect_to root_path, alert: I18n.t("admin.flash.admin_access_only")
98
+ end
99
+ end
100
+ end