upload_d_n_d 0.9.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.

Potentially problematic release.


This version of upload_d_n_d might be problematic. Click here for more details.

Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +229 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/upload_d_n_d_manifest.js +3 -0
  6. data/app/assets/stylesheets/upload_d_n_d/application.css +604 -0
  7. data/app/components/upload_d_n_d/upload_component.html.erb +53 -0
  8. data/app/components/upload_d_n_d/upload_component.rb +204 -0
  9. data/app/controllers/concerns/upload_d_n_d/rate_limiting.rb +89 -0
  10. data/app/controllers/upload_d_n_d/attachments_controller.rb +218 -0
  11. data/app/controllers/upload_d_n_d/base_attachments_controller.rb +250 -0
  12. data/app/controllers/upload_d_n_d/temporary_attachments_controller.rb +200 -0
  13. data/app/helpers/upload_d_n_d/application_helper.rb +51 -0
  14. data/app/helpers/upload_d_n_d/controllers/helpers.rb +21 -0
  15. data/app/javascript/upload_d_n_d/controllers/preview_controller.js +217 -0
  16. data/app/javascript/upload_d_n_d/controllers/uploads_controller.js +396 -0
  17. data/app/javascript/upload_d_n_d/index.js +13 -0
  18. data/app/jobs/upload_d_n_d/application_job.rb +4 -0
  19. data/app/jobs/upload_d_n_d/cleanup_temporary_attachments_job.rb +17 -0
  20. data/app/jobs/upload_d_n_d/purge_orphan_blobs_job.rb +25 -0
  21. data/app/jobs/upload_d_n_d/purge_rejected_blob_job.rb +24 -0
  22. data/app/models/upload_d_n_d/application_record.rb +5 -0
  23. data/app/models/upload_d_n_d/temporary_attachment.rb +129 -0
  24. data/app/views/upload_d_n_d/shared/_file_icon.html.erb +9 -0
  25. data/app/views/upload_d_n_d/shared/_files_list.html.erb +63 -0
  26. data/app/views/upload_d_n_d/shared/_temp_files_list.html.erb +53 -0
  27. data/config/importmap.rb +5 -0
  28. data/config/initializers/inflections.rb +11 -0
  29. data/config/locales/upload_d_n_d.en.yml +80 -0
  30. data/config/locales/upload_d_n_d.fr.yml +80 -0
  31. data/config/routes.rb +12 -0
  32. data/docs/CHANGELOG.md +282 -0
  33. data/lib/generators/upload_d_n_d/POLICY_README +49 -0
  34. data/lib/generators/upload_d_n_d/USAGE +80 -0
  35. data/lib/generators/upload_d_n_d/install_generator.rb +174 -0
  36. data/lib/generators/upload_d_n_d/mime_type_helper.rb +106 -0
  37. data/lib/generators/upload_d_n_d/model_generator.rb +313 -0
  38. data/lib/generators/upload_d_n_d/policy_generator.rb +25 -0
  39. data/lib/generators/upload_d_n_d/templates/POST_INSTALL +62 -0
  40. data/lib/generators/upload_d_n_d/templates/concerns/controllers_uploadable.rb +115 -0
  41. data/lib/generators/upload_d_n_d/templates/concerns/upload_authorization.rb +91 -0
  42. data/lib/generators/upload_d_n_d/templates/concerns/uploadable.rb +101 -0
  43. data/lib/generators/upload_d_n_d/templates/controller.rb.tt +60 -0
  44. data/lib/generators/upload_d_n_d/templates/helpers/upload_helper.rb +105 -0
  45. data/lib/generators/upload_d_n_d/templates/initializers/upload_d_n_d.rb +69 -0
  46. data/lib/generators/upload_d_n_d/templates/migrations/create_upload_d_n_d_temporary_attachments.rb +18 -0
  47. data/lib/generators/upload_d_n_d/templates/policies/upload_d_n_d_attachment_policy.rb +37 -0
  48. data/lib/generators/upload_d_n_d/templates/policies/upload_d_n_d_temporary_attachment_policy.rb +56 -0
  49. data/lib/generators/upload_d_n_d/templates/validators/file_validator.rb +171 -0
  50. data/lib/tasks/upload_d_n_d_storage.rake +27 -0
  51. data/lib/tasks/upload_d_n_d_tasks.rake +20 -0
  52. data/lib/upload_d_n_d/attachment_lookup.rb +24 -0
  53. data/lib/upload_d_n_d/authorization/can_can_can_adapter.rb +29 -0
  54. data/lib/upload_d_n_d/authorization/no_authorization.rb +13 -0
  55. data/lib/upload_d_n_d/authorization/pundit_adapter.rb +41 -0
  56. data/lib/upload_d_n_d/authorization/unconfigured.rb +15 -0
  57. data/lib/upload_d_n_d/authorization.rb +66 -0
  58. data/lib/upload_d_n_d/configuration.rb +97 -0
  59. data/lib/upload_d_n_d/controllers/uploadable.rb +271 -0
  60. data/lib/upload_d_n_d/engine.rb +50 -0
  61. data/lib/upload_d_n_d/field_constraints.rb +92 -0
  62. data/lib/upload_d_n_d/field_reference.rb +44 -0
  63. data/lib/upload_d_n_d/file_validator.rb +156 -0
  64. data/lib/upload_d_n_d/mime_types.rb +75 -0
  65. data/lib/upload_d_n_d/orphan_blobs.rb +151 -0
  66. data/lib/upload_d_n_d/rejected_blob.rb +42 -0
  67. data/lib/upload_d_n_d/session_id.rb +25 -0
  68. data/lib/upload_d_n_d/uploadable.rb +117 -0
  69. data/lib/upload_d_n_d/version.rb +3 -0
  70. data/lib/upload_d_n_d.rb +62 -0
  71. metadata +150 -0
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UploadDND
4
+ module UploadAuthorization
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :authorize_upload!, only: [ :create ]
9
+ before_action :authorize_destroy!, only: [ :destroy ]
10
+ end
11
+
12
+ private
13
+
14
+ # Vérifie si l'utilisateur peut uploader sur cet attribut
15
+ # Peut être surchargée dans votre controller
16
+ def authorize_upload!
17
+ # Si vous utilisez Pundit
18
+ if defined?(Pundit)
19
+ authorize_with_pundit
20
+ # Si vous utilisez CanCanCan
21
+ elsif defined?(CanCan)
22
+ authorize_with_cancan
23
+ else
24
+ # Autorisation par défaut : l'utilisateur doit être connecté
25
+ authorize_basic_upload!
26
+ end
27
+ end
28
+
29
+ # Vérifie si l'utilisateur peut supprimer ce fichier
30
+ def authorize_destroy!
31
+ if defined?(Pundit)
32
+ authorize @temp_attachment || @attachment
33
+ elsif defined?(CanCan)
34
+ authorize! :destroy, @temp_attachment || @attachment
35
+ else
36
+ authorize_basic_destroy!
37
+ end
38
+ end
39
+
40
+ # Autorisation basique : utilisateur connecté
41
+ def authorize_basic_upload!
42
+ return if current_user.present?
43
+
44
+ render json: { error: "Vous devez être connecté pour uploader des fichiers" },
45
+ status: :unauthorized
46
+ end
47
+
48
+ def authorize_basic_destroy!
49
+ # Vérifie que le fichier appartient à la session ou à l'utilisateur
50
+ return if owner_matches_current_user?
51
+
52
+ render json: { error: "Vous ne pouvez pas supprimer ce fichier" },
53
+ status: :forbidden
54
+ end
55
+
56
+ def authorize_with_pundit
57
+ record = find_or_build_record
58
+ authorize record, :update?
59
+ end
60
+
61
+ def authorize_with_cancan
62
+ record = find_or_build_record
63
+ authorize! :update, record
64
+ end
65
+
66
+ def owner_matches_current_user?
67
+ if @temp_attachment
68
+ @temp_attachment.session_id == UploadDND::SessionId.for(session) ||
69
+ @temp_attachment.user_id == current_user&.id
70
+ elsif @attachment
71
+ @attachment.record.user_id == current_user&.id if @attachment.record.respond_to?(:user_id)
72
+ end
73
+ end
74
+
75
+ def find_or_build_record
76
+ if params[:record_gid].present?
77
+ GlobalID::Locator.locate(params[:record_gid])
78
+ else
79
+ # Pour les uploads temporaires, on ne peut pas vraiment autoriser
80
+ # car le record n'existe pas encore
81
+ nil
82
+ end
83
+ end
84
+
85
+ def current_user
86
+ # À adapter selon votre système d'authentification
87
+ # Par défaut, on suppose que vous utilisez Devise ou similaire
88
+ respond_to?(:current_user, true) ? super : nil
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UploadDND
4
+ module Uploadable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Callbacks pour attacher les fichiers temporaires après création
9
+ after_create :attach_temporary_uploads
10
+ end
11
+
12
+ # Stocke temporairement le session_id pour l'upload
13
+ # Utilise une variable d'instance privée plutôt qu'attr_accessor
14
+ def temp_upload_session_id=(value)
15
+ @_temp_upload_session_id = value
16
+ end
17
+
18
+ def temp_upload_session_id
19
+ @_temp_upload_session_id
20
+ end
21
+
22
+ class_methods do
23
+ # Déclare un champ uploadable avec validation
24
+ #
25
+ # @param attribute_name [Symbol] le nom de l'attribut (ex: :avatar, :documents)
26
+ # @param options [Hash] les options de validation
27
+ # @option options [Array<Symbol>] :accept extensions acceptées (ex: [:jpg, :png, :pdf])
28
+ # @option options [Array<String>] :content_type types MIME autorisés (alternative à :accept)
29
+ # @option options [Integer] :max_size la taille max en bytes
30
+ # @option options [Integer] :max_files le nombre max de fichiers (pour has_many)
31
+ # @option options [Boolean] :multiple autoriser plusieurs fichiers (déclare has_many_attached)
32
+ # @option options [String] :message message d'erreur personnalisé
33
+ # @option options [Boolean] :strict_mime_check active la détection MIME côté serveur (anti-spoofing)
34
+ # @option options [String] :spoofing_message message d'erreur personnalisé pour les erreurs de spoofing
35
+ #
36
+ # @example Image unique (has_one_attached déclaré automatiquement)
37
+ # class User < ApplicationRecord
38
+ # include UploadDND::Uploadable
39
+ #
40
+ # uploadable :avatar,
41
+ # accept: [:jpg, :png],
42
+ # max_size: 5.megabytes,
43
+ # strict_mime_check: true
44
+ # end
45
+ #
46
+ # @example Documents multiples (has_many_attached déclaré automatiquement)
47
+ # class Project < ApplicationRecord
48
+ # include UploadDND::Uploadable
49
+ #
50
+ # uploadable :documents,
51
+ # accept: [:pdf, :doc, :docx],
52
+ # max_size: 10.megabytes,
53
+ # multiple: true,
54
+ # max_files: 5,
55
+ # strict_mime_check: true
56
+ # end
57
+ def uploadable(attribute_name, **options)
58
+ validates attribute_name,
59
+ file: options.slice(:content_type, :max_size, :max_files, :message, :strict_mime_check, :spoofing_message)
60
+
61
+ # Stocke les options pour usage ultérieur
62
+ (@uploadable_attributes ||= {})[attribute_name] = options
63
+ end
64
+
65
+ def uploadable_attributes
66
+ @uploadable_attributes || {}
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def attach_temporary_uploads
73
+ return unless temp_upload_session_id.present?
74
+ return unless UploadDND::TemporaryAttachment.exists?(session_id: temp_upload_session_id)
75
+
76
+ UploadDND::TemporaryAttachment.where(session_id: temp_upload_session_id).find_each do |temp_attachment|
77
+ attribute_name = temp_attachment.attribute_name
78
+ next unless respond_to?(attribute_name)
79
+
80
+ # SÉCURITÉ : Détection serveur du type d'attachment (has_one vs has_many)
81
+ attachment = send(attribute_name)
82
+
83
+ if attachment.is_a?(ActiveStorage::Attached::Many)
84
+ # has_many_attached: ajouter à la collection
85
+ attachment.attach(temp_attachment.blob.blob)
86
+ elsif attachment.is_a?(ActiveStorage::Attached::One)
87
+ # has_one_attached: utiliser = (remplace automatiquement)
88
+ # Ne transférer que le PREMIER fichier temporaire si plusieurs existent
89
+ unless attachment.attached?
90
+ send("#{attribute_name}=", temp_attachment.blob.blob)
91
+ end
92
+ end
93
+
94
+ temp_attachment.destroy
95
+ end
96
+
97
+ # Nettoie le session_id après utilisation
98
+ @_temp_upload_session_id = nil
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing do -%>
4
+ class <%= plural_name.camelize %>Controller < ApplicationController
5
+ include UploadDND::Controllers::Uploadable
6
+
7
+ before_action :set_<%= singular_name %>, only: [:show, :edit, :update, :destroy, :attach_file, :remove_file]
8
+
9
+ def index
10
+ @<%= plural_name %> = <%= class_name %>.all
11
+ end
12
+
13
+ def show
14
+ end
15
+
16
+ def new
17
+ @<%= singular_name %> = <%= class_name %>.new
18
+ end
19
+
20
+ def edit
21
+ end
22
+
23
+ def create
24
+ @<%= singular_name %> = <%= class_name %>.new(<%= singular_name %>_params)
25
+
26
+ # Lie les uploads temporaires (session) au record
27
+ wire_temporary_uploads(@<%= singular_name %>)
28
+
29
+ if @<%= singular_name %>.save
30
+ redirect_to @<%= singular_name %>, notice: '<%= class_name %> créé avec succès'
31
+ else
32
+ render :new, status: :unprocessable_entity
33
+ end
34
+ end
35
+
36
+ def update
37
+ if @<%= singular_name %>.update(<%= singular_name %>_params)
38
+ redirect_to @<%= singular_name %>, notice: '<%= class_name %> mis à jour avec succès'
39
+ else
40
+ render :edit, status: :unprocessable_entity
41
+ end
42
+ end
43
+
44
+ def destroy
45
+ @<%= singular_name %>.destroy
46
+ redirect_to <%= plural_name %>_url, notice: '<%= class_name %> supprimé avec succès'
47
+ end
48
+
49
+ private
50
+
51
+ def set_<%= singular_name %>
52
+ @<%= singular_name %> = <%= class_name %>.find(params[:id])
53
+ end
54
+
55
+ def <%= singular_name %>_params
56
+ params.require(:<%= singular_name %>).permit(:name) # Ajoutez vos attributs ici
57
+ # Note: les fichiers uploadés ne sont PAS dans strong params (gérés par UploadDND)
58
+ end
59
+ end
60
+ <% end -%>
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UploadDND
4
+ module UploadHelper
5
+ # Rendu du composant d'upload
6
+ #
7
+ # @param attribute_name [Symbol] le nom de l'attribut (ex: :avatar, :documents)
8
+ # @param record [ApplicationRecord, nil] le record à attacher (nil pour upload temporaire)
9
+ # @param options [Hash] options supplémentaires
10
+ # @option options [Boolean] :multiple (false) autoriser plusieurs fichiers
11
+ # @option options [String] :accepted_types types de fichiers acceptés (ex: '.jpg,.png')
12
+ # @option options [Integer] :max_size taille max en bytes
13
+ # @option options [String] :label label personnalisé
14
+ # @option options [Hash] :validation_options options passées au FileValidator
15
+ #
16
+ # @example Upload simple
17
+ # <%= upload_field :avatar, record: @user %>
18
+ #
19
+ # @example Upload multiple avec validation
20
+ # <%= upload_field :documents,
21
+ # record: @project,
22
+ # multiple: true,
23
+ # accepted_types: '.pdf,.docx',
24
+ # max_size: 10.megabytes %>
25
+ #
26
+ # @example Upload temporaire (record pas encore créé)
27
+ # <%= upload_field :logo, accepted_types: '.png,.jpg' %>
28
+ #
29
+ def upload_field(attribute_name, record: nil, **options)
30
+ render UploadDND::UploadComponent.new(
31
+ attribute_name: attribute_name,
32
+ record: record,
33
+ user: current_user_for_upload,
34
+ **options
35
+ )
36
+ end
37
+
38
+ # Génère un champ caché avec le session_id pour les uploads temporaires.
39
+ # À placer dans les formulaires qui créent un nouveau record avec upload.
40
+ def upload_session_field
41
+ tag.input(type: "hidden", name: "temp_upload_session_id", value: UploadDND::SessionId.for(session))
42
+ end
43
+
44
+ # Affiche la liste des fichiers uploadés
45
+ #
46
+ # @param attribute_name [Symbol] le nom de l'attribut
47
+ # @param record [ApplicationRecord] le record
48
+ #
49
+ def uploaded_files_list(attribute_name, record:)
50
+ return unless record&.respond_to?(attribute_name)
51
+
52
+ attachments = record.send(attribute_name)
53
+ return unless attachments.attached?
54
+
55
+ files = attachments.respond_to?(:each) ? attachments : [ attachments ]
56
+
57
+ render partial: "upload_d_n_d/shared/files_list",
58
+ locals: { files: files, attribute_name: attribute_name }
59
+ end
60
+
61
+ # Récupère l'utilisateur courant pour l'upload
62
+ # Peut être surchargé dans votre application
63
+ def current_user_for_upload
64
+ respond_to?(:current_user, true) ? current_user : nil
65
+ end
66
+
67
+ # Icône selon le type de fichier
68
+ def file_icon_class(filename)
69
+ extension = File.extname(filename).downcase
70
+
71
+ case extension
72
+ when ".pdf"
73
+ "fa-file-pdf"
74
+ when ".doc", ".docx"
75
+ "fa-file-word"
76
+ when ".xls", ".xlsx"
77
+ "fa-file-excel"
78
+ when ".jpg", ".jpeg", ".png", ".gif", ".svg"
79
+ "fa-file-image"
80
+ when ".zip", ".rar", ".7z"
81
+ "fa-file-archive"
82
+ when ".mp4", ".avi", ".mov"
83
+ "fa-file-video"
84
+ else
85
+ "fa-file"
86
+ end
87
+ end
88
+
89
+ # Badge de couleur selon le type
90
+ def file_type_badge_class(content_type)
91
+ case content_type
92
+ when /^image\//
93
+ "badge-success"
94
+ when /^application\/pdf/
95
+ "badge-danger"
96
+ when /^application\/.*word/
97
+ "badge-primary"
98
+ when /^application\/.*excel/, /^application\/.*spreadsheet/
99
+ "badge-success"
100
+ else
101
+ "badge-secondary"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Configuration de UploadDND
4
+ UploadDND.configure do |config|
5
+ # Taille maximale par défaut des fichiers (en bytes)
6
+ # Peut être surchargé au niveau du composant
7
+ config.max_size = 10.megabytes
8
+
9
+ # Types de fichiers acceptés par défaut
10
+ # Peut être surchargé au niveau du composant
11
+ config.accepted_types = ".pdf,.doc,.docx,.jpg,.jpeg,.png,.gif"
12
+
13
+ # Vérification stricte du type MIME (anti-spoofing)
14
+ # Analyse le contenu réel du fichier via Marcel (plus coûteux)
15
+ # config.strict_mime_check = false
16
+
17
+ # Couleur principale du thème (utilisée pour les boutons, progress bar, etc.)
18
+ config.main_color = "#E97451"
19
+
20
+ # Autoriser les uploads sans authentification (déconseillé en production)
21
+ # config.allow_anonymous_uploads = false
22
+
23
+ # Durée de conservation des fichiers temporaires (en jours)
24
+ # Les fichiers temporaires non attachés seront supprimés après ce délai
25
+ # config.temporary_files_retention = 7
26
+
27
+ # Un fichier refusé par le serveur (taille, type, validation...) est purgé
28
+ # s'il a été envoyé dans cette fenêtre et n'est rattaché à rien.
29
+ # nil désactive la purge des fichiers refusés.
30
+ # config.rejected_blob_purge_window = 1.hour
31
+
32
+ # Nettoyage des blobs orphelins (opt-in : UploadDND::PurgeOrphanBlobsJob ou
33
+ # la tâche rake upload_d_n_d:purge_orphans). PostgreSQL requis.
34
+ # config.orphan_blob_min_age = 2.days
35
+ # Limite le scan des citations aux colonnes de texte riche (borne la mémoire).
36
+ # Sans liste, toutes les colonnes texte/JSON sont scannées.
37
+ # config.orphan_scan_columns = ["projects.description", "pages.body_translations"]
38
+ # Motifs d'adresse supplémentaires (hôte/bucket du service de stockage).
39
+ # config.orphan_scan_patterns = ["%votre-bucket.example.com%"]
40
+ # config.orphan_scan_excluded_tables = []
41
+
42
+ # Autorisation : :pundit ou :cancancan (détection automatique), ou :none
43
+ # pour s'en passer. Sans adaptateur utilisable, tout est refusé (403).
44
+ # config.authorization_adapter = :pundit # ou :cancancan, ou :none
45
+
46
+ # Validateur personnalisé
47
+ # Par défaut, utilise FileValidator si disponible
48
+ # config.validator_class = 'FileValidator'
49
+
50
+ # Configuration du stockage ActiveStorage (optionnel)
51
+ # Exemple: forcer l'utilisation d'un service spécifique
52
+ # config.active_storage_service = :amazon
53
+
54
+ # Callbacks personnalisés
55
+ # config.on_upload_success = ->(blob, user) {
56
+ # # Logger ou notifier
57
+ # Rails.logger.info "File uploaded: #{blob.filename} by #{user&.email}"
58
+ # }
59
+ #
60
+ # config.on_upload_error = ->(error, user) {
61
+ # # Logger ou notifier
62
+ # Rails.logger.error "Upload failed: #{error.message} for #{user&.email}"
63
+ # }
64
+
65
+ # Rate limiting (compteur basé sur Rails.cache ; aucune gem requise)
66
+ # config.rate_limit_enabled = true
67
+ # config.rate_limit_max_requests = 10
68
+ # config.rate_limit_period = 1.minute
69
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateUploadDNDTemporaryAttachments < ActiveRecord::Migration[7.2]
4
+ def change
5
+ create_table :upload_d_n_d_temporary_attachments do |t|
6
+ t.string :session_id, null: false
7
+ t.string :attribute_name, null: false
8
+ t.integer :user_id
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :upload_d_n_d_temporary_attachments, :session_id
14
+ add_index :upload_d_n_d_temporary_attachments, :user_id
15
+ add_index :upload_d_n_d_temporary_attachments, [ :session_id, :attribute_name ],
16
+ name: "index_temp_attachments_on_session_and_attribute"
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Policy Pundit pour l'upload de fichiers permanents avec UploadDND
4
+ #
5
+ # Cette policy contrôle qui peut attacher et supprimer des fichiers
6
+ # sur des records existants (via le moteur UploadDND).
7
+ #
8
+ # Le controller du moteur autorise via `authorize :upload_d_n_d_attachment`
9
+ # (record symbolique), ce qui amène Pundit à chercher cette classe.
10
+ #
11
+ class UploadDNDAttachmentPolicy < ApplicationPolicy
12
+ # Détermine qui peut attacher un fichier à un record existant
13
+ #
14
+ # Par défaut : tous les utilisateurs authentifiés
15
+ # Exemples de personnalisation :
16
+ # - Seulement les admins : user&.admin?
17
+ # - Seulement certains rôles : user&.admin? || user&.editor?
18
+ #
19
+ def create?
20
+ # PERSONNALISEZ CETTE MÉTHODE selon vos besoins
21
+ user.present?
22
+ end
23
+
24
+ # Détermine qui peut modifier les fichiers d'un record
25
+ #
26
+ def update?
27
+ # PERSONNALISEZ CETTE MÉTHODE selon vos besoins
28
+ user.present?
29
+ end
30
+
31
+ # Détermine qui peut supprimer un fichier d'un record
32
+ #
33
+ def destroy?
34
+ # PERSONNALISEZ CETTE MÉTHODE selon vos besoins
35
+ user.present?
36
+ end
37
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Policy Pundit pour l'upload de fichiers temporaires avec UploadDND
4
+ #
5
+ # Cette policy contrôle qui peut uploader et supprimer des fichiers temporaires.
6
+ # Personnalisez les méthodes ci-dessous selon vos besoins d'autorisation.
7
+ #
8
+ class UploadDNDTemporaryAttachmentPolicy < ApplicationPolicy
9
+ # Détermine qui peut créer (uploader) un fichier temporaire
10
+ #
11
+ # Par défaut : tous les utilisateurs authentifiés
12
+ # Exemples de personnalisation :
13
+ # - Seulement les admins : user&.admin?
14
+ # - Seulement certains rôles : user&.admin? || user&.editor?
15
+ # - Selon un attribut : user&.can_upload?
16
+ #
17
+ def create?
18
+ # PERSONNALISEZ CETTE MÉTHODE selon vos besoins
19
+ user.present?
20
+ end
21
+
22
+ # Détermine qui peut supprimer un fichier temporaire
23
+ #
24
+ # Par défaut : tous les utilisateurs authentifiés
25
+ # La vérification de propriété (session) est déjà faite dans le controller
26
+ #
27
+ def destroy?
28
+ # PERSONNALISEZ CETTE MÉTHODE selon vos besoins
29
+ user.present?
30
+ end
31
+
32
+ # Détermine qui peut nettoyer les fichiers temporaires expirés
33
+ # (utilisé par les tâches cron/jobs)
34
+ #
35
+ def cleanup?
36
+ # PERSONNALISEZ CETTE MÉTHODE selon vos besoins
37
+ user&.admin?
38
+ end
39
+
40
+ # Exemple d'autorisation plus stricte (commenté par défaut)
41
+ # Décommentez et adaptez selon vos besoins
42
+ #
43
+ # def create?
44
+ # return false unless user.present?
45
+ #
46
+ # # Seulement les admins et éditeurs
47
+ # user.admin? || user.editor?
48
+ # end
49
+ #
50
+ # def destroy?
51
+ # return false unless user.present?
52
+ #
53
+ # # Seulement les admins, éditeurs, ou l'équipe régionale
54
+ # user.admin? || user.editor? || user.team&.regional_team?
55
+ # end
56
+ end