@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 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
- - extension name
32
- - size
33
- - dimension (width, height)
34
- - created date
35
- - orientation
36
- - mime type
37
- - gps
32
+ ## Setup
38
33
 
39
- ### Variants
34
+ Install and configure the package:
40
35
 
41
- Configure differents images sizes and formats
36
+ ```sh
37
+ node ace add @jrmc/adonis-attachment
38
+ ```
42
39
 
43
- ### Regenerate (coming soon)
40
+ ## Sample
44
41
 
45
- Regenerate variantes files
42
+ Simple upload file
46
43
 
47
- ### Drive
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
- Use [@adonisjs/drive](https://docs.adonisjs.com/guides/digging-deeper/drive) for private file and cloud services
51
+ class User extends compose(BaseModel, Attachmentable) { // [!code highlight]
52
+ @attachment() // [!code highlight]
53
+ declare avatar: Attachment // [!code highlight]
54
+ }
55
+ ```
50
56
 
51
- ### Jobs queue (coming soon)
57
+ ---
52
58
 
53
- Couple with a job queue (recommended, optional)
59
+ ```ts
60
+ // app/controllers/users_controller.ts
61
+ import { attachmentManager } from '@jrmc/adonis-attachment' // [!code focus]
54
62
 
55
- ## Setup
63
+ class UsersController {
64
+ public store({ request }: HttpContext) {
65
+ const avatar = request.file('avatar')! // [!code focus]
66
+ const user = new User()
56
67
 
57
- Install and configure the package:
68
+ user.avatar = await attachmentManager.createFromFile(avatar) // [!code focus]
69
+ await user.save()
70
+ }
71
+ }
72
+ ```
58
73
 
59
- ```sh
60
- node ace add @jrmc/adonis-attachment
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, logger, drive);
24
+ this.#manager = new AttachmentManager(config, drive);
26
25
  return this.#manager;
27
26
  });
28
27
  }
@@ -125,7 +125,7 @@ async function videoExif(input) {
125
125
  dimension: {
126
126
  width: data.streams[0].width,
127
127
  height: data.streams[0].height,
128
- }
128
+ },
129
129
  });
130
130
  });
131
131
  });
@@ -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, logger: LoggerService, drive: DriveService);
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 { Exception } from '@poppinss/utils';
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, logger, drive) {
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 Exception(`Cannot create attachment from database response. Missing attribute "${attribute}"`);
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 Error("It's not a valid file");
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
- try {
98
- if (Buffer.isBuffer(attachment.input)) {
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
- catch (err) {
106
- this.#logger.error({ err }, 'Error send file');
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
- try {
112
- const filePath = attachment.path;
113
- try {
114
- await attachment.getDisk().delete(filePath);
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
  }
@@ -83,7 +83,7 @@ export class Attachment extends AttachmentBase {
83
83
  this.variants.forEach((v) => {
84
84
  v.setOptions({
85
85
  ...this.options,
86
- variants: []
86
+ variants: [],
87
87
  });
88
88
  });
89
89
  }
@@ -65,7 +65,7 @@ export class AttachmentBase {
65
65
  if (this.url) {
66
66
  return {
67
67
  ...this.toObject(),
68
- url: this.url
68
+ url: this.url,
69
69
  };
70
70
  }
71
71
  return this.toObject();
@@ -27,7 +27,7 @@ export default class AutodetectConverter extends Converter {
27
27
  if (converter) {
28
28
  return await converter.handle({
29
29
  input,
30
- options
30
+ options,
31
31
  });
32
32
  }
33
33
  }
@@ -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
- const lib = await use('libreoffice-file-converter');
13
- if (lib) {
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
- const sharp = await use('sharp');
12
- if (sharp) {
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
- const nodePoppler = await use('node-poppler');
16
- if (nodePoppler) {
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
- const ffmpeg = await use('fluent-ffmpeg');
16
- if (ffmpeg) {
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 ? defaultConfig.preComputeUrl : defaultOptionsDecorator.preComputeUrl,
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 = getAttachmentAttributeNames(modelInstance);
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
@@ -10,7 +10,7 @@ export const defaultOptionsDecorator = {
10
10
  preComputeUrl: false,
11
11
  variants: [],
12
12
  meta: true,
13
- rename: true
13
+ rename: true,
14
14
  };
15
15
  export const defaultStateAttributeMixin = {
16
16
  attached: [],
@@ -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 (error) {
73
- logger.error({ err: error }, `Dependence missing, please install ${module}`);
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.0",
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
+ }