@programisto/edrm-storage 1.0.3 → 1.0.5
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.
package/README.md
CHANGED
|
@@ -7,6 +7,12 @@ export declare class S3StorageProvider implements StorageProvider {
|
|
|
7
7
|
constructor(region: string, accessKeyId?: string, secretAccessKey?: string);
|
|
8
8
|
initUpload(bucket: string, key: string, contentType: string, expiresIn?: number): Promise<UploadInitResponse>;
|
|
9
9
|
getFileMetadata(bucket: string, key: string): Promise<FileMetadata>;
|
|
10
|
+
/**
|
|
11
|
+
* Encode un nom de fichier pour l'en-tête Content-Disposition selon RFC 6266
|
|
12
|
+
* Gère les caractères spéciaux (accents, etc.) en utilisant l'encodage UTF-8
|
|
13
|
+
* AWS S3 nécessite un encodage strict pour éviter l'erreur "Header value cannot be represented using ISO-8859-1"
|
|
14
|
+
*/
|
|
15
|
+
private encodeContentDispositionFilename;
|
|
10
16
|
getDownloadUrl(bucket: string, key: string, filename?: string, expiresIn?: number): Promise<DownloadUrlResponse>;
|
|
11
17
|
deleteFile(bucket: string, key: string): Promise<void>;
|
|
12
18
|
copyFile(sourceBucket: string, sourceKey: string, destBucket: string, destKey: string): Promise<void>;
|
|
@@ -48,11 +48,34 @@ export class S3StorageProvider {
|
|
|
48
48
|
metadata: response.Metadata
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Encode un nom de fichier pour l'en-tête Content-Disposition selon RFC 6266
|
|
53
|
+
* Gère les caractères spéciaux (accents, etc.) en utilisant l'encodage UTF-8
|
|
54
|
+
* AWS S3 nécessite un encodage strict pour éviter l'erreur "Header value cannot be represented using ISO-8859-1"
|
|
55
|
+
*/
|
|
56
|
+
encodeContentDispositionFilename(filename) {
|
|
57
|
+
// Vérifier si le nom de fichier contient des caractères non-ASCII
|
|
58
|
+
const hasNonAscii = /[^\x20-\x7E]/.test(filename);
|
|
59
|
+
if (hasNonAscii) {
|
|
60
|
+
// Pour les noms avec caractères non-ASCII, utiliser uniquement filename* avec UTF-8
|
|
61
|
+
// Cela évite les problèmes avec S3 qui rejette les caractères non-ASCII dans filename
|
|
62
|
+
const encodedFilename = encodeURIComponent(filename)
|
|
63
|
+
.replace(/'/g, '%27')
|
|
64
|
+
.replace(/\(/g, '%28')
|
|
65
|
+
.replace(/\)/g, '%29');
|
|
66
|
+
// Format RFC 6266 : utiliser uniquement filename* pour éviter les erreurs S3
|
|
67
|
+
return `attachment; filename*=UTF-8''${encodedFilename}`;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Pour les noms ASCII uniquement, utiliser le format simple
|
|
71
|
+
return `attachment; filename="${filename}"`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
51
74
|
async getDownloadUrl(bucket, key, filename, expiresIn = 3600) {
|
|
52
75
|
const command = new GetObjectCommand({
|
|
53
76
|
Bucket: bucket,
|
|
54
77
|
Key: key,
|
|
55
|
-
ResponseContentDisposition: filename ?
|
|
78
|
+
ResponseContentDisposition: filename ? this.encodeContentDispositionFilename(filename) : undefined
|
|
56
79
|
});
|
|
57
80
|
const url = await getSignedUrl(this.client, command, {
|
|
58
81
|
expiresIn
|
|
@@ -14,7 +14,60 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
14
14
|
const userOptions = {
|
|
15
15
|
requireAuth: true
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @swagger
|
|
19
|
+
* /files/init:
|
|
20
|
+
* post:
|
|
21
|
+
* summary: Initialiser un upload de fichier
|
|
22
|
+
* description: Crée un enregistrement de fichier et retourne une URL signée pour commencer l’upload. Authentification utilisateur requise.
|
|
23
|
+
* tags: [Stockage]
|
|
24
|
+
* requestBody:
|
|
25
|
+
* required: true
|
|
26
|
+
* content:
|
|
27
|
+
* application/json:
|
|
28
|
+
* schema:
|
|
29
|
+
* type: object
|
|
30
|
+
* required: [originalName, mimeType, size, tenantId, entityName, entityId]
|
|
31
|
+
* properties:
|
|
32
|
+
* originalName:
|
|
33
|
+
* type: string
|
|
34
|
+
* description: Nom d’origine du fichier
|
|
35
|
+
* mimeType:
|
|
36
|
+
* type: string
|
|
37
|
+
* description: Type MIME du fichier
|
|
38
|
+
* size:
|
|
39
|
+
* type: integer
|
|
40
|
+
* description: Taille du fichier en octets
|
|
41
|
+
* tenantId:
|
|
42
|
+
* type: string
|
|
43
|
+
* description: Identifiant du tenant
|
|
44
|
+
* entityName:
|
|
45
|
+
* type: string
|
|
46
|
+
* description: Entité métier associée (ex. "invoice")
|
|
47
|
+
* entityId:
|
|
48
|
+
* type: string
|
|
49
|
+
* description: Identifiant de l’entité métier associée
|
|
50
|
+
* provider:
|
|
51
|
+
* type: string
|
|
52
|
+
* enum: [S3]
|
|
53
|
+
* default: S3
|
|
54
|
+
* description: Provider de stockage
|
|
55
|
+
* metadata:
|
|
56
|
+
* type: object
|
|
57
|
+
* description: Métadonnées supplémentaires
|
|
58
|
+
* tags:
|
|
59
|
+
* type: array
|
|
60
|
+
* items:
|
|
61
|
+
* type: string
|
|
62
|
+
* description: Liste de tags libres
|
|
63
|
+
* responses:
|
|
64
|
+
* 200:
|
|
65
|
+
* description: URL signée et informations d’initialisation
|
|
66
|
+
* 400:
|
|
67
|
+
* description: Paramètres manquants ou invalides
|
|
68
|
+
* 500:
|
|
69
|
+
* description: Erreur serveur
|
|
70
|
+
*/
|
|
18
71
|
this.post('/files/init', userOptions, async (req, res) => {
|
|
19
72
|
try {
|
|
20
73
|
const { originalName, mimeType, size, tenantId, entityName, entityId, provider = 'S3', metadata, tags } = req.body;
|
|
@@ -38,7 +91,26 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
38
91
|
});
|
|
39
92
|
}
|
|
40
93
|
});
|
|
41
|
-
|
|
94
|
+
/**
|
|
95
|
+
* @swagger
|
|
96
|
+
* /files/{fileId}/complete:
|
|
97
|
+
* post:
|
|
98
|
+
* summary: Finaliser un upload
|
|
99
|
+
* description: Marque l’upload comme terminé et met à jour le statut du fichier. Authentification utilisateur requise.
|
|
100
|
+
* tags: [Stockage]
|
|
101
|
+
* parameters:
|
|
102
|
+
* - in: path
|
|
103
|
+
* name: fileId
|
|
104
|
+
* required: true
|
|
105
|
+
* schema:
|
|
106
|
+
* type: string
|
|
107
|
+
* description: Identifiant du fichier
|
|
108
|
+
* responses:
|
|
109
|
+
* 200:
|
|
110
|
+
* description: Upload finalisé avec succès
|
|
111
|
+
* 500:
|
|
112
|
+
* description: Erreur serveur
|
|
113
|
+
*/
|
|
42
114
|
this.post('/files/:fileId/complete', userOptions, async (req, res) => {
|
|
43
115
|
try {
|
|
44
116
|
const { fileId } = req.params;
|
|
@@ -56,7 +128,37 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
56
128
|
});
|
|
57
129
|
}
|
|
58
130
|
});
|
|
59
|
-
|
|
131
|
+
/**
|
|
132
|
+
* @swagger
|
|
133
|
+
* /files/{fileId}/download:
|
|
134
|
+
* get:
|
|
135
|
+
* summary: Obtenir une URL de téléchargement
|
|
136
|
+
* description: Retourne une URL signée pour télécharger le fichier. Authentification utilisateur requise.
|
|
137
|
+
* tags: [Stockage]
|
|
138
|
+
* parameters:
|
|
139
|
+
* - in: path
|
|
140
|
+
* name: fileId
|
|
141
|
+
* required: true
|
|
142
|
+
* schema:
|
|
143
|
+
* type: string
|
|
144
|
+
* description: Identifiant du fichier
|
|
145
|
+
* - in: query
|
|
146
|
+
* name: filename
|
|
147
|
+
* schema:
|
|
148
|
+
* type: string
|
|
149
|
+
* description: Nom de fichier à proposer au téléchargement
|
|
150
|
+
* - in: query
|
|
151
|
+
* name: expiresIn
|
|
152
|
+
* schema:
|
|
153
|
+
* type: integer
|
|
154
|
+
* default: 3600
|
|
155
|
+
* description: Durée de validité du lien en secondes
|
|
156
|
+
* responses:
|
|
157
|
+
* 200:
|
|
158
|
+
* description: URL de téléchargement générée
|
|
159
|
+
* 500:
|
|
160
|
+
* description: Erreur serveur
|
|
161
|
+
*/
|
|
60
162
|
this.get('/files/:fileId/download', userOptions, async (req, res) => {
|
|
61
163
|
try {
|
|
62
164
|
const { fileId } = req.params;
|
|
@@ -75,7 +177,26 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
75
177
|
});
|
|
76
178
|
}
|
|
77
179
|
});
|
|
78
|
-
|
|
180
|
+
/**
|
|
181
|
+
* @swagger
|
|
182
|
+
* /files/{fileId}:
|
|
183
|
+
* delete:
|
|
184
|
+
* summary: Supprimer un fichier
|
|
185
|
+
* description: Supprime un fichier et ses métadonnées. Authentification et permission Admin_ManageFiles requises.
|
|
186
|
+
* tags: [Stockage]
|
|
187
|
+
* parameters:
|
|
188
|
+
* - in: path
|
|
189
|
+
* name: fileId
|
|
190
|
+
* required: true
|
|
191
|
+
* schema:
|
|
192
|
+
* type: string
|
|
193
|
+
* description: Identifiant du fichier
|
|
194
|
+
* responses:
|
|
195
|
+
* 200:
|
|
196
|
+
* description: Fichier supprimé
|
|
197
|
+
* 500:
|
|
198
|
+
* description: Erreur serveur
|
|
199
|
+
*/
|
|
79
200
|
this.delete('/files/:fileId', adminOptions, async (req, res) => {
|
|
80
201
|
try {
|
|
81
202
|
const { fileId } = req.params;
|
|
@@ -93,7 +214,53 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
93
214
|
});
|
|
94
215
|
}
|
|
95
216
|
});
|
|
96
|
-
|
|
217
|
+
/**
|
|
218
|
+
* @swagger
|
|
219
|
+
* /files:
|
|
220
|
+
* get:
|
|
221
|
+
* summary: Lister les fichiers
|
|
222
|
+
* description: Retourne une liste paginée des fichiers avec filtres. Authentification et permission Admin_ManageFiles requises.
|
|
223
|
+
* tags: [Stockage]
|
|
224
|
+
* parameters:
|
|
225
|
+
* - in: query
|
|
226
|
+
* name: tenantId
|
|
227
|
+
* schema:
|
|
228
|
+
* type: string
|
|
229
|
+
* description: Filtrer par tenant
|
|
230
|
+
* - in: query
|
|
231
|
+
* name: entityName
|
|
232
|
+
* schema:
|
|
233
|
+
* type: string
|
|
234
|
+
* description: Filtrer par entité
|
|
235
|
+
* - in: query
|
|
236
|
+
* name: entityId
|
|
237
|
+
* schema:
|
|
238
|
+
* type: string
|
|
239
|
+
* description: Filtrer par identifiant d’entité
|
|
240
|
+
* - in: query
|
|
241
|
+
* name: status
|
|
242
|
+
* schema:
|
|
243
|
+
* type: string
|
|
244
|
+
* enum: [PENDING, COMPLETED, FAILED]
|
|
245
|
+
* description: Filtrer par statut de fichier
|
|
246
|
+
* - in: query
|
|
247
|
+
* name: page
|
|
248
|
+
* schema:
|
|
249
|
+
* type: integer
|
|
250
|
+
* default: 1
|
|
251
|
+
* description: Numéro de page
|
|
252
|
+
* - in: query
|
|
253
|
+
* name: limit
|
|
254
|
+
* schema:
|
|
255
|
+
* type: integer
|
|
256
|
+
* default: 20
|
|
257
|
+
* description: Nombre d’éléments par page
|
|
258
|
+
* responses:
|
|
259
|
+
* 200:
|
|
260
|
+
* description: Liste paginée de fichiers
|
|
261
|
+
* 500:
|
|
262
|
+
* description: Erreur serveur
|
|
263
|
+
*/
|
|
97
264
|
this.get('/files', adminOptions, async (req, res) => {
|
|
98
265
|
try {
|
|
99
266
|
const { tenantId, entityName, entityId, status, page = 1, limit = 20 } = req.query;
|
|
@@ -111,7 +278,26 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
111
278
|
});
|
|
112
279
|
}
|
|
113
280
|
});
|
|
114
|
-
|
|
281
|
+
/**
|
|
282
|
+
* @swagger
|
|
283
|
+
* /files/{fileId}:
|
|
284
|
+
* get:
|
|
285
|
+
* summary: Récupérer un fichier par ID
|
|
286
|
+
* description: Retourne les métadonnées d’un fichier. Authentification utilisateur requise.
|
|
287
|
+
* tags: [Stockage]
|
|
288
|
+
* parameters:
|
|
289
|
+
* - in: path
|
|
290
|
+
* name: fileId
|
|
291
|
+
* required: true
|
|
292
|
+
* schema:
|
|
293
|
+
* type: string
|
|
294
|
+
* description: Identifiant du fichier
|
|
295
|
+
* responses:
|
|
296
|
+
* 200:
|
|
297
|
+
* description: Détails du fichier
|
|
298
|
+
* 500:
|
|
299
|
+
* description: Erreur serveur
|
|
300
|
+
*/
|
|
115
301
|
this.get('/files/:fileId', userOptions, async (req, res) => {
|
|
116
302
|
try {
|
|
117
303
|
const { fileId } = req.params;
|
|
@@ -129,7 +315,26 @@ class EdrmStorageRouter extends EnduranceRouter {
|
|
|
129
315
|
});
|
|
130
316
|
}
|
|
131
317
|
});
|
|
132
|
-
|
|
318
|
+
/**
|
|
319
|
+
* @swagger
|
|
320
|
+
* /files/s3-callback:
|
|
321
|
+
* post:
|
|
322
|
+
* summary: Callback d’événements S3
|
|
323
|
+
* description: Réception et traitement des webhooks S3. Authentification non requise.
|
|
324
|
+
* tags: [Stockage]
|
|
325
|
+
* requestBody:
|
|
326
|
+
* required: false
|
|
327
|
+
* content:
|
|
328
|
+
* application/json:
|
|
329
|
+
* schema:
|
|
330
|
+
* type: object
|
|
331
|
+
* description: Payload du webhook S3
|
|
332
|
+
* responses:
|
|
333
|
+
* 200:
|
|
334
|
+
* description: Callback traité
|
|
335
|
+
* 500:
|
|
336
|
+
* description: Erreur serveur
|
|
337
|
+
*/
|
|
133
338
|
this.post('/files/s3-callback', { requireAuth: false, permissions: [] }, async (req, res) => {
|
|
134
339
|
try {
|
|
135
340
|
// Cette route peut être utilisée pour recevoir des webhooks S3
|