@jrmc/adonis-attachment 2.3.0 → 2.3.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.
- package/README.md +38 -20
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/providers/attachment_provider.js +1 -2
- package/build/src/adapters/exif.js +1 -1
- package/build/src/attachment_manager.d.ts +1 -2
- package/build/src/attachment_manager.js +21 -42
- package/build/src/attachments/attachment.js +1 -1
- package/build/src/attachments/attachment_base.js +1 -1
- package/build/src/converters/autodetect_converter.js +1 -1
- package/build/src/converters/document_thumbnail_converter.js +8 -4
- package/build/src/converters/image_converter.js +6 -2
- package/build/src/converters/pdf_thumbnail_converter.js +7 -3
- package/build/src/converters/video_thumbnail_converter.js +8 -5
- package/build/src/decorators/attachment.js +3 -1
- package/build/src/errors.d.ts +58 -0
- package/build/src/errors.js +56 -0
- package/build/src/mixins/attachmentable.d.ts +1 -1
- package/build/src/mixins/attachmentable.js +5 -9
- package/build/src/utils/default_values.js +1 -1
- package/build/src/utils/helpers.d.ts +2 -0
- package/build/src/utils/helpers.js +20 -3
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
|
|
|
7
7
|
## Links
|
|
8
8
|
|
|
9
9
|
[View documentation](https://adonis-attachment.jrmc.dev/)
|
|
10
|
+
|
|
10
11
|
[Discord](https://discord.gg/89eMn2vB)
|
|
11
12
|
|
|
12
13
|
[ChangeLog](https://adonis-attachment.jrmc.dev/changelog.html)
|
|
@@ -22,41 +23,58 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
|
|
|
22
23
|
- [x] documents thumbnail
|
|
23
24
|
- [x] videos thumbnail
|
|
24
25
|
- [ ] command regenerate
|
|
26
|
+
- [ ] command make:convert
|
|
25
27
|
- [x] adonis-drive/flydrive
|
|
26
28
|
- [ ] jobs queue
|
|
27
29
|
- [x] serialize
|
|
28
30
|
|
|
29
|
-
### Meta data list (if available)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
- size
|
|
33
|
-
- dimension (width, height)
|
|
34
|
-
- created date
|
|
35
|
-
- orientation
|
|
36
|
-
- mime type
|
|
37
|
-
- gps
|
|
32
|
+
## Setup
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
Install and configure the package:
|
|
40
35
|
|
|
41
|
-
|
|
36
|
+
```sh
|
|
37
|
+
node ace add @jrmc/adonis-attachment
|
|
38
|
+
```
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
## Sample
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
Simple upload file
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
```ts
|
|
45
|
+
// app/models/user.ts
|
|
46
|
+
import { BaseModel } from '@adonisjs/lucid/orm'
|
|
47
|
+
import { compose } from '@adonisjs/core/helpers'
|
|
48
|
+
import { attachment, Attachmentable } from '@jrmc/adonis-attachment'
|
|
49
|
+
import type { Attachment } from '@jrmc/adonis-attachment/types/attachment' // [!code highlight]
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
class User extends compose(BaseModel, Attachmentable) { // [!code highlight]
|
|
52
|
+
@attachment() // [!code highlight]
|
|
53
|
+
declare avatar: Attachment // [!code highlight]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
---
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
```ts
|
|
60
|
+
// app/controllers/users_controller.ts
|
|
61
|
+
import { attachmentManager } from '@jrmc/adonis-attachment' // [!code focus]
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
class UsersController {
|
|
64
|
+
public store({ request }: HttpContext) {
|
|
65
|
+
const avatar = request.file('avatar')! // [!code focus]
|
|
66
|
+
const user = new User()
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
user.avatar = await attachmentManager.createFromFile(avatar) // [!code focus]
|
|
69
|
+
await user.save()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
```edge
|
|
77
|
+
<img src="{{ await user.avatar.getUrl() }}" loading="lazy" alt="" />
|
|
61
78
|
```
|
|
62
79
|
|
|
80
|
+
Read [documentation](https://adonis-attachment.jrmc.dev/) for advanced usage(thumbnail video/pdf/doc, create from buffer/base64...)
|
package/build/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export { Attachment } from './src/attachments/attachment.js';
|
|
|
4
4
|
export { attachment } from './src/decorators/attachment.js';
|
|
5
5
|
export { defineConfig } from './src/define_config.js';
|
|
6
6
|
export { Attachmentable } from './src/mixins/attachmentable.js';
|
|
7
|
+
export * as errors from './src/errors.js';
|
|
7
8
|
export { attachmentManager };
|
package/build/index.js
CHANGED
|
@@ -4,4 +4,5 @@ export { Attachment } from './src/attachments/attachment.js';
|
|
|
4
4
|
export { attachment } from './src/decorators/attachment.js';
|
|
5
5
|
export { defineConfig } from './src/define_config.js';
|
|
6
6
|
export { Attachmentable } from './src/mixins/attachmentable.js';
|
|
7
|
+
export * as errors from './src/errors.js';
|
|
7
8
|
export { attachmentManager };
|
|
@@ -17,12 +17,11 @@ export default class AttachmentProvider {
|
|
|
17
17
|
const { AttachmentManager } = await import('../src/attachment_manager.js');
|
|
18
18
|
const attachmentConfig = this.app.config.get('attachment');
|
|
19
19
|
const config = await configProvider.resolve(this.app, attachmentConfig);
|
|
20
|
-
const logger = await this.app.container.make('logger');
|
|
21
20
|
const drive = await this.app.container.make('drive.manager');
|
|
22
21
|
if (!config) {
|
|
23
22
|
throw new RuntimeException('Invalid config exported from "config/attachment.ts" file. Make sure to use the defineConfig method');
|
|
24
23
|
}
|
|
25
|
-
this.#manager = new AttachmentManager(config,
|
|
24
|
+
this.#manager = new AttachmentManager(config, drive);
|
|
26
25
|
return this.#manager;
|
|
27
26
|
});
|
|
28
27
|
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import type { LoggerService } from '@adonisjs/core/types';
|
|
8
7
|
import type { DriveService, SignedURLOptions } from '@adonisjs/drive/types';
|
|
9
8
|
import type { MultipartFile } from '@adonisjs/core/bodyparser';
|
|
10
9
|
import type { AttachmentBase, Attachment as AttachmentType } from './types/attachment.js';
|
|
@@ -13,7 +12,7 @@ import { Attachment } from './attachments/attachment.js';
|
|
|
13
12
|
import Converter from './converters/converter.js';
|
|
14
13
|
export declare class AttachmentManager {
|
|
15
14
|
#private;
|
|
16
|
-
constructor(config: ResolvedAttachmentConfig,
|
|
15
|
+
constructor(config: ResolvedAttachmentConfig, drive: DriveService);
|
|
17
16
|
getConfig(): ResolvedAttachmentConfig;
|
|
18
17
|
createFromDbResponse(response: any): Attachment | null;
|
|
19
18
|
createFromFile(file: MultipartFile): Promise<Attachment>;
|
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
7
|
+
import * as errors from './errors.js';
|
|
8
8
|
import { Attachment } from './attachments/attachment.js';
|
|
9
|
-
import { createAttachmentAttributes } from './utils/helpers.js';
|
|
9
|
+
import { createAttachmentAttributes, isBase64 } from './utils/helpers.js';
|
|
10
10
|
import { exif } from './adapters/exif.js';
|
|
11
11
|
const REQUIRED_ATTRIBUTES = ['name', 'size', 'extname', 'mimeType'];
|
|
12
12
|
export class AttachmentManager {
|
|
13
|
-
#logger;
|
|
14
13
|
#config;
|
|
15
14
|
#drive;
|
|
16
|
-
constructor(config,
|
|
17
|
-
this.#logger = logger;
|
|
15
|
+
constructor(config, drive) {
|
|
18
16
|
this.#drive = drive;
|
|
19
17
|
this.#config = config;
|
|
20
18
|
}
|
|
@@ -28,7 +26,7 @@ export class AttachmentManager {
|
|
|
28
26
|
const attributes = typeof response === 'string' ? JSON.parse(response) : response;
|
|
29
27
|
REQUIRED_ATTRIBUTES.forEach((attribute) => {
|
|
30
28
|
if (attributes[attribute] === undefined) {
|
|
31
|
-
throw new
|
|
29
|
+
throw new errors.E_CANNOT_CREATE_ATTACHMENT([attribute]);
|
|
32
30
|
}
|
|
33
31
|
});
|
|
34
32
|
return new Attachment(this.#drive, attributes);
|
|
@@ -41,16 +39,22 @@ export class AttachmentManager {
|
|
|
41
39
|
size: file.size,
|
|
42
40
|
};
|
|
43
41
|
if (!file.tmpPath) {
|
|
44
|
-
throw new
|
|
42
|
+
throw new errors.ENOENT();
|
|
45
43
|
}
|
|
46
44
|
return new Attachment(this.#drive, attributes, file.tmpPath);
|
|
47
45
|
}
|
|
48
46
|
async createFromBuffer(buffer, name) {
|
|
47
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
48
|
+
throw new errors.E_ISNOT_BUFFER();
|
|
49
|
+
}
|
|
49
50
|
const attributes = await createAttachmentAttributes(buffer, name);
|
|
50
51
|
return new Attachment(this.#drive, attributes, buffer);
|
|
51
52
|
}
|
|
52
53
|
async createFromBase64(data, name) {
|
|
53
54
|
const base64Data = data.replace(/^data:([A-Za-z-+\/]+);base64,/, '');
|
|
55
|
+
if (!isBase64(base64Data)) {
|
|
56
|
+
throw new errors.E_ISNOT_BASE64();
|
|
57
|
+
}
|
|
54
58
|
const buffer = Buffer.from(base64Data, 'base64');
|
|
55
59
|
return await this.createFromBuffer(buffer, name);
|
|
56
60
|
}
|
|
@@ -94,47 +98,22 @@ export class AttachmentManager {
|
|
|
94
98
|
else {
|
|
95
99
|
attachment.meta = undefined;
|
|
96
100
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
await attachment.getDisk().put(destinationPath, attachment.input);
|
|
100
|
-
}
|
|
101
|
-
else if (attachment.input) {
|
|
102
|
-
await attachment.getDisk().copyFromFs(attachment.input, destinationPath);
|
|
103
|
-
}
|
|
101
|
+
if (Buffer.isBuffer(attachment.input)) {
|
|
102
|
+
await attachment.getDisk().put(destinationPath, attachment.input);
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
else if (attachment.input) {
|
|
105
|
+
await attachment.getDisk().copyFromFs(attachment.input, destinationPath);
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
async delete(attachment) {
|
|
110
109
|
if (attachment.path) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
const filePath = attachment.path;
|
|
111
|
+
await attachment.getDisk().delete(filePath);
|
|
112
|
+
if (attachment instanceof Attachment) {
|
|
113
|
+
if (attachment.variants) {
|
|
114
|
+
const variantPath = attachment.variants[0].folder;
|
|
115
|
+
await attachment.getDisk().deleteAll(variantPath);
|
|
115
116
|
}
|
|
116
|
-
catch (accessError) {
|
|
117
|
-
if (accessError.code === 'ENOENT') {
|
|
118
|
-
this.#logger.warn(`File not found: ${filePath}`);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
throw accessError;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (attachment instanceof Attachment) {
|
|
125
|
-
if (attachment.variants) {
|
|
126
|
-
const variantPath = attachment.variants[0].folder;
|
|
127
|
-
try {
|
|
128
|
-
await attachment.getDisk().deleteAll(variantPath);
|
|
129
|
-
}
|
|
130
|
-
catch (rmError) {
|
|
131
|
-
this.#logger.error(`Failed to remove variants folder: ${rmError.message}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
this.#logger.error(error);
|
|
138
117
|
}
|
|
139
118
|
}
|
|
140
119
|
}
|
|
@@ -4,24 +4,28 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
+
import logger from '@adonisjs/core/services/logger';
|
|
7
8
|
import Converter from './converter.js';
|
|
8
9
|
import ImageConverter from './image_converter.js';
|
|
9
10
|
import { use } from '../utils/helpers.js';
|
|
10
11
|
export default class DocumentThumbnailConverter extends Converter {
|
|
11
12
|
async handle({ input, options }) {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
try {
|
|
14
|
+
const lib = await use('libreoffice-file-converter');
|
|
14
15
|
const LibreOfficeFileConverter = lib.LibreOfficeFileConverter;
|
|
15
16
|
const outputBuffer = await this.documentToImage(LibreOfficeFileConverter, input);
|
|
16
17
|
if (options && outputBuffer) {
|
|
17
18
|
const converter = new ImageConverter();
|
|
18
19
|
return await converter.handle({
|
|
19
20
|
input: outputBuffer,
|
|
20
|
-
options
|
|
21
|
+
options,
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
return outputBuffer;
|
|
24
25
|
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
logger.error({ err });
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
30
|
async documentToImage(LibreOfficeFileConverter, input) {
|
|
27
31
|
let binaryPaths = undefined;
|
|
@@ -32,7 +36,7 @@ export default class DocumentThumbnailConverter extends Converter {
|
|
|
32
36
|
childProcessOptions: {
|
|
33
37
|
timeout: 60 * 1000,
|
|
34
38
|
},
|
|
35
|
-
binaryPaths
|
|
39
|
+
binaryPaths,
|
|
36
40
|
});
|
|
37
41
|
if (Buffer.isBuffer(input)) {
|
|
38
42
|
const output = await libreOfficeFileConverter.convert({
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
6
|
*/
|
|
7
|
+
import logger from '@adonisjs/core/services/logger';
|
|
7
8
|
import Converter from './converter.js';
|
|
8
9
|
import { use } from '../utils/helpers.js';
|
|
9
10
|
export default class ImageConverter extends Converter {
|
|
10
11
|
async handle({ input, options }) {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
try {
|
|
13
|
+
const sharp = await use('sharp');
|
|
13
14
|
const resize = options?.resize || {};
|
|
14
15
|
let format = options?.format || 'webp';
|
|
15
16
|
let formatoptions = {};
|
|
@@ -24,5 +25,8 @@ export default class ImageConverter extends Converter {
|
|
|
24
25
|
.toBuffer();
|
|
25
26
|
return buffer;
|
|
26
27
|
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
logger.error({ err });
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -7,24 +7,28 @@
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { cuid } from '@adonisjs/core/helpers';
|
|
10
|
+
import logger from '@adonisjs/core/services/logger';
|
|
10
11
|
import Converter from './converter.js';
|
|
11
12
|
import ImageConverter from './image_converter.js';
|
|
12
13
|
import { use } from '../utils/helpers.js';
|
|
13
14
|
export default class PdfThumbnailConverter extends Converter {
|
|
14
15
|
async handle({ input, options }) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
const nodePoppler = await use('node-poppler');
|
|
17
18
|
const Poppler = nodePoppler.Poppler;
|
|
18
19
|
const filePath = await this.pdfToImage(Poppler, input);
|
|
19
20
|
if (options && filePath) {
|
|
20
21
|
const converter = new ImageConverter();
|
|
21
22
|
return await converter.handle({
|
|
22
23
|
input: filePath,
|
|
23
|
-
options
|
|
24
|
+
options,
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
return filePath;
|
|
27
28
|
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
logger.error({ err });
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
async pdfToImage(Poppler, input) {
|
|
30
34
|
let binPath = null;
|
|
@@ -7,25 +7,29 @@
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { cuid } from '@adonisjs/core/helpers';
|
|
10
|
+
import logger from '@adonisjs/core/services/logger';
|
|
10
11
|
import Converter from './converter.js';
|
|
11
12
|
import ImageConverter from './image_converter.js';
|
|
12
13
|
import { bufferToTempFile, use } from '../utils/helpers.js';
|
|
13
14
|
export default class VideoThumbnailConvert extends Converter {
|
|
14
15
|
async handle({ input, options }) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
const ffmpeg = await use('fluent-ffmpeg');
|
|
17
18
|
const filePath = await this.videoToImage(ffmpeg, input);
|
|
18
19
|
if (options && filePath) {
|
|
19
20
|
const converter = new ImageConverter();
|
|
20
21
|
return await converter.handle({
|
|
21
22
|
input: filePath,
|
|
22
|
-
options
|
|
23
|
+
options,
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
else {
|
|
26
27
|
return filePath;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
logger.error({ err });
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
async videoToImage(ffmpeg, input) {
|
|
31
35
|
let file = input;
|
|
@@ -45,8 +49,7 @@ export default class VideoThumbnailConvert extends Converter {
|
|
|
45
49
|
count: 1,
|
|
46
50
|
filename,
|
|
47
51
|
folder,
|
|
48
|
-
})
|
|
49
|
-
.on('end', () => {
|
|
52
|
+
}).on('end', () => {
|
|
50
53
|
resolve(path.join(folder, filename));
|
|
51
54
|
});
|
|
52
55
|
});
|
|
@@ -16,7 +16,9 @@ export const attachment = (options) => {
|
|
|
16
16
|
const defaultOptions = {
|
|
17
17
|
meta: defaultConfig.meta !== undefined ? defaultConfig.meta : defaultOptionsDecorator.meta,
|
|
18
18
|
rename: defaultConfig.rename !== undefined ? defaultConfig.rename : defaultOptionsDecorator.rename,
|
|
19
|
-
preComputeUrl: defaultConfig.preComputeUrl !== undefined
|
|
19
|
+
preComputeUrl: defaultConfig.preComputeUrl !== undefined
|
|
20
|
+
? defaultConfig.preComputeUrl
|
|
21
|
+
: defaultOptionsDecorator.preComputeUrl,
|
|
20
22
|
};
|
|
21
23
|
if (!options || options?.meta === undefined) {
|
|
22
24
|
options.meta = defaultOptions.meta;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Unable to write file to the destination
|
|
9
|
+
*/
|
|
10
|
+
export declare const E_CANNOT_WRITE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
11
|
+
/**
|
|
12
|
+
* Unable to read file
|
|
13
|
+
*/
|
|
14
|
+
export declare const E_CANNOT_READ_FILE: new (args: [key: string], options?: ErrorOptions) => import(
|
|
15
|
+
/**
|
|
16
|
+
* Unable to read file
|
|
17
|
+
*/
|
|
18
|
+
"@adonisjs/core/exceptions").Exception;
|
|
19
|
+
/**
|
|
20
|
+
* Unable to delete file
|
|
21
|
+
*/
|
|
22
|
+
export declare const E_CANNOT_DELETE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
23
|
+
/**
|
|
24
|
+
* Unable to set file visibility
|
|
25
|
+
*/
|
|
26
|
+
export declare const E_CANNOT_SET_VISIBILITY: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
27
|
+
/**
|
|
28
|
+
* Unable to generate URL for a file
|
|
29
|
+
*/
|
|
30
|
+
export declare const E_CANNOT_GENERATE_URL: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
31
|
+
/**
|
|
32
|
+
* The file key has unallowed set of characters
|
|
33
|
+
*/
|
|
34
|
+
export declare const E_UNALLOWED_CHARACTERS: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
35
|
+
/**
|
|
36
|
+
* Key post normalization leads to an empty string
|
|
37
|
+
*/
|
|
38
|
+
export declare const E_INVALID_KEY: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
39
|
+
/**
|
|
40
|
+
* Missing package
|
|
41
|
+
*/
|
|
42
|
+
export declare const E_MISSING_PACKAGE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
43
|
+
/**
|
|
44
|
+
* Unable to create Attachment Object
|
|
45
|
+
*/
|
|
46
|
+
export declare const E_CANNOT_CREATE_ATTACHMENT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
47
|
+
/**
|
|
48
|
+
* Is not a Buffer
|
|
49
|
+
*/
|
|
50
|
+
export declare const E_ISNOT_BUFFER: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
51
|
+
/**
|
|
52
|
+
* Is not a Base64
|
|
53
|
+
*/
|
|
54
|
+
export declare const E_ISNOT_BASE64: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
55
|
+
/**
|
|
56
|
+
* Unable to read file
|
|
57
|
+
*/
|
|
58
|
+
export declare const ENOENT: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import { createError } from '@adonisjs/core/exceptions';
|
|
8
|
+
import { errors } from 'flydrive';
|
|
9
|
+
/**
|
|
10
|
+
* Unable to write file to the destination
|
|
11
|
+
*/
|
|
12
|
+
export const E_CANNOT_WRITE_FILE = errors.E_CANNOT_WRITE_FILE;
|
|
13
|
+
/**
|
|
14
|
+
* Unable to read file
|
|
15
|
+
*/
|
|
16
|
+
export const E_CANNOT_READ_FILE = errors.E_CANNOT_READ_FILE;
|
|
17
|
+
/**
|
|
18
|
+
* Unable to delete file
|
|
19
|
+
*/
|
|
20
|
+
export const E_CANNOT_DELETE_FILE = errors.E_CANNOT_DELETE_FILE;
|
|
21
|
+
/**
|
|
22
|
+
* Unable to set file visibility
|
|
23
|
+
*/
|
|
24
|
+
export const E_CANNOT_SET_VISIBILITY = errors.E_CANNOT_SET_VISIBILITY;
|
|
25
|
+
/**
|
|
26
|
+
* Unable to generate URL for a file
|
|
27
|
+
*/
|
|
28
|
+
export const E_CANNOT_GENERATE_URL = errors.E_CANNOT_GENERATE_URL;
|
|
29
|
+
/**
|
|
30
|
+
* The file key has unallowed set of characters
|
|
31
|
+
*/
|
|
32
|
+
export const E_UNALLOWED_CHARACTERS = errors.E_UNALLOWED_CHARACTERS;
|
|
33
|
+
/**
|
|
34
|
+
* Key post normalization leads to an empty string
|
|
35
|
+
*/
|
|
36
|
+
export const E_INVALID_KEY = errors.E_INVALID_KEY;
|
|
37
|
+
/**
|
|
38
|
+
* Missing package
|
|
39
|
+
*/
|
|
40
|
+
export const E_MISSING_PACKAGE = createError('Missing package, please install "%s"', 'E_MISSING_PACKAGE');
|
|
41
|
+
/**
|
|
42
|
+
* Unable to create Attachment Object
|
|
43
|
+
*/
|
|
44
|
+
export const E_CANNOT_CREATE_ATTACHMENT = createError('Cannot create attachment from database response. Missing attribute "%s"', 'E_CANNOT_CREATE_ATTACHMENT');
|
|
45
|
+
/**
|
|
46
|
+
* Is not a Buffer
|
|
47
|
+
*/
|
|
48
|
+
export const E_ISNOT_BUFFER = createError('Is not a Buffer', 'E_ISNOT_BUFFER');
|
|
49
|
+
/**
|
|
50
|
+
* Is not a Base64
|
|
51
|
+
*/
|
|
52
|
+
export const E_ISNOT_BASE64 = createError('Is not a Base64', 'E_ISNOT_BASE64');
|
|
53
|
+
/**
|
|
54
|
+
* Unable to read file
|
|
55
|
+
*/
|
|
56
|
+
export const ENOENT = createError('File not found', 'ENOENT');
|
|
@@ -369,7 +369,6 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<typeof
|
|
|
369
369
|
related<Name_2 extends undefined>(relation: Name_2): any[Name_2] extends import("@adonisjs/lucid/types/relations").ModelRelations<import("@adonisjs/lucid/types/model").LucidModel, import("@adonisjs/lucid/types/model").LucidModel> ? any[Name_2]["client"] : never;
|
|
370
370
|
}): Promise<void>;
|
|
371
371
|
find: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<null | InstanceType<T>>;
|
|
372
|
-
create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
373
372
|
readonly booted: boolean;
|
|
374
373
|
$columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
|
|
375
374
|
$relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
|
|
@@ -416,6 +415,7 @@ export declare const Attachmentable: <Model extends NormalizeConstructor<typeof
|
|
|
416
415
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelPaginatorContract<InstanceType<Model_1>>, "paginate">): void;
|
|
417
416
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
|
|
418
417
|
};
|
|
418
|
+
create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
419
419
|
createMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
420
420
|
findOrFail: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, value: any, options?: import("@adonisjs/lucid/types/model").ModelAdapterOptions) => Promise<InstanceType<T>>;
|
|
421
421
|
findBy: {
|
|
@@ -10,9 +10,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
10
10
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
11
11
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
12
12
|
};
|
|
13
|
-
import { beforeSave, afterSave, beforeDelete, afterFind, afterFetch, afterPaginate } from '@adonisjs/lucid/orm';
|
|
14
|
-
import { persistAttachment, commit, rollback, generateVariants, preComputeUrl } from '../utils/actions.js';
|
|
15
|
-
import { clone, getAttachmentAttributeNames } from '../utils/helpers.js';
|
|
13
|
+
import { beforeSave, afterSave, beforeDelete, afterFind, afterFetch, afterPaginate, } from '@adonisjs/lucid/orm';
|
|
14
|
+
import { persistAttachment, commit, rollback, generateVariants, preComputeUrl, } from '../utils/actions.js';
|
|
15
|
+
import { clone, getAttachmentAttributeNames, getDirtyAttachmentAttributeNames, } from '../utils/helpers.js';
|
|
16
16
|
import { defaultStateAttributeMixin } from '../utils/default_values.js';
|
|
17
17
|
export const Attachmentable = (superclass) => {
|
|
18
18
|
class ModelWithAttachment extends superclass {
|
|
@@ -27,7 +27,7 @@ export const Attachmentable = (superclass) => {
|
|
|
27
27
|
await Promise.all(modelInstances.map((row) => this.afterFindHook(row)));
|
|
28
28
|
}
|
|
29
29
|
static async beforeSaveHook(modelInstance) {
|
|
30
|
-
const attachmentAttributeNames =
|
|
30
|
+
const attachmentAttributeNames = getDirtyAttachmentAttributeNames(modelInstance);
|
|
31
31
|
/**
|
|
32
32
|
* Empty previous $attachments
|
|
33
33
|
*/
|
|
@@ -35,11 +35,7 @@ export const Attachmentable = (superclass) => {
|
|
|
35
35
|
/**
|
|
36
36
|
* Set attributes Attachment type modified
|
|
37
37
|
*/
|
|
38
|
-
attachmentAttributeNames.forEach((attributeName) =>
|
|
39
|
-
if (modelInstance.$dirty[attributeName]) {
|
|
40
|
-
modelInstance.$attachments.attributesModified.push(attributeName);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
38
|
+
attachmentAttributeNames.forEach((attributeName) => modelInstance.$attachments.attributesModified.push(attributeName));
|
|
43
39
|
/**
|
|
44
40
|
* Persist attachments before saving the model to the database. This
|
|
45
41
|
* way if file saving fails we will not write anything to the
|
|
@@ -8,6 +8,7 @@ import type { LucidOptions } from '../types/attachment.js';
|
|
|
8
8
|
import type { Input } from '../types/input.js';
|
|
9
9
|
import type { ModelWithAttachment } from '../types/mixin.js';
|
|
10
10
|
export declare function getAttachmentAttributeNames(modelInstance: ModelWithAttachment): string[];
|
|
11
|
+
export declare function getDirtyAttachmentAttributeNames(modelInstance: ModelWithAttachment): string[];
|
|
11
12
|
export declare function getOptions(modelInstance: ModelWithAttachment, attributeName: string): LucidOptions;
|
|
12
13
|
export declare function createAttachmentAttributes(input: Input, name?: string): Promise<{
|
|
13
14
|
originalName: string;
|
|
@@ -19,3 +20,4 @@ export declare function cleanObject(obj: any): any;
|
|
|
19
20
|
export declare function clone(object: Object): any;
|
|
20
21
|
export declare function use(module: string): Promise<any>;
|
|
21
22
|
export declare function bufferToTempFile(input: Buffer): Promise<string>;
|
|
23
|
+
export declare function isBase64(str: string): boolean;
|
|
@@ -9,13 +9,17 @@ import path from 'node:path';
|
|
|
9
9
|
import fs from 'fs/promises';
|
|
10
10
|
import { cuid } from '@adonisjs/core/helpers';
|
|
11
11
|
import string from '@adonisjs/core/helpers/string';
|
|
12
|
-
import logger from '@adonisjs/core/services/logger';
|
|
13
12
|
import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
|
|
14
13
|
import { Attachment } from '../attachments/attachment.js';
|
|
14
|
+
import * as errors from '../errors.js';
|
|
15
15
|
import { optionsSym } from './symbols.js';
|
|
16
16
|
export function getAttachmentAttributeNames(modelInstance) {
|
|
17
17
|
return Object.keys(modelInstance.$attributes).filter((attr) => modelInstance.$attributes[attr] instanceof Attachment);
|
|
18
18
|
}
|
|
19
|
+
export function getDirtyAttachmentAttributeNames(modelInstance) {
|
|
20
|
+
return Object.keys(modelInstance.$dirty).filter((attr) => modelInstance.$dirty[attr] instanceof Attachment ||
|
|
21
|
+
modelInstance.$original[attr] instanceof Attachment);
|
|
22
|
+
}
|
|
19
23
|
export function getOptions(modelInstance, attributeName) {
|
|
20
24
|
return modelInstance.constructor.prototype[optionsSym]?.[attributeName];
|
|
21
25
|
}
|
|
@@ -69,8 +73,8 @@ export async function use(module) {
|
|
|
69
73
|
}
|
|
70
74
|
return result;
|
|
71
75
|
}
|
|
72
|
-
catch (
|
|
73
|
-
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new errors.E_MISSING_PACKAGE([module], { cause: err });
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
export async function bufferToTempFile(input) {
|
|
@@ -79,3 +83,16 @@ export async function bufferToTempFile(input) {
|
|
|
79
83
|
await fs.writeFile(tempFilePath, input);
|
|
80
84
|
return tempFilePath;
|
|
81
85
|
}
|
|
86
|
+
export function isBase64(str) {
|
|
87
|
+
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
88
|
+
if (!base64Regex.test(str)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
Buffer.from(str, 'base64').toString();
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrmc/adonis-attachment",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Turn any field on your Lucid model to an attachment data type",
|
|
6
6
|
"engines": {
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"./attachment_provider": "./build/providers/attachment_provider.js"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@poppinss/utils": "^6.7.3",
|
|
60
59
|
"exifreader": "^4.23.3",
|
|
61
60
|
"file-type": "^19.4.0"
|
|
62
61
|
},
|
|
@@ -87,4 +86,4 @@
|
|
|
87
86
|
"volta": {
|
|
88
87
|
"node": "20.17.0"
|
|
89
88
|
}
|
|
90
|
-
}
|
|
89
|
+
}
|