@jrmc/adonis-attachment 3.1.0 → 3.2.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.
Files changed (45) hide show
  1. package/README.md +4 -0
  2. package/build/src/adapters/meta.d.ts +3 -0
  3. package/build/src/adapters/meta.js +40 -0
  4. package/build/src/attachment_manager.d.ts +5 -5
  5. package/build/src/attachment_manager.js +50 -29
  6. package/build/src/attachments/attachment.js +9 -2
  7. package/build/src/decorators/attachment.js +25 -0
  8. package/build/src/errors.d.ts +15 -15
  9. package/build/src/mixins/attachmentable.d.ts +5 -314
  10. package/build/src/mixins/attachmentable.js +10 -88
  11. package/build/src/types/converter.d.ts +22 -1
  12. package/build/src/types/input.d.ts +5 -0
  13. package/build/src/utils/helpers.d.ts +0 -7
  14. package/build/src/utils/helpers.js +4 -28
  15. package/build/src/utils/hooks.d.ts +11 -0
  16. package/build/src/utils/hooks.js +96 -0
  17. package/package.json +18 -16
  18. package/build/bin/test.d.ts +0 -1
  19. package/build/bin/test.js +0 -34
  20. package/build/tests/attachment-manager.spec.d.ts +0 -7
  21. package/build/tests/attachment-manager.spec.js +0 -234
  22. package/build/tests/attachment.spec.d.ts +0 -7
  23. package/build/tests/attachment.spec.js +0 -16
  24. package/build/tests/commands.spec.d.ts +0 -7
  25. package/build/tests/commands.spec.js +0 -58
  26. package/build/tests/fixtures/converters/image_converter.d.ts +0 -12
  27. package/build/tests/fixtures/converters/image_converter.js +0 -12
  28. package/build/tests/fixtures/factories/user.d.ts +0 -8
  29. package/build/tests/fixtures/factories/user.js +0 -19
  30. package/build/tests/fixtures/factories/user_with_variants.d.ts +0 -8
  31. package/build/tests/fixtures/factories/user_with_variants.js +0 -19
  32. package/build/tests/fixtures/migrations/create_users_table.d.ts +0 -12
  33. package/build/tests/fixtures/migrations/create_users_table.js +0 -23
  34. package/build/tests/fixtures/models/user.d.ts +0 -466
  35. package/build/tests/fixtures/models/user.js +0 -36
  36. package/build/tests/fixtures/models/user_with_variants.d.ts +0 -465
  37. package/build/tests/fixtures/models/user_with_variants.js +0 -33
  38. package/build/tests/helpers/app.d.ts +0 -29
  39. package/build/tests/helpers/app.js +0 -104
  40. package/build/tests/helpers/index.d.ts +0 -7
  41. package/build/tests/helpers/index.js +0 -7
  42. package/build/tests/options.spec.d.ts +0 -7
  43. package/build/tests/options.spec.js +0 -126
  44. package/build/tests/variants.spec.d.ts +0 -7
  45. package/build/tests/variants.spec.js +0 -21
package/README.md CHANGED
@@ -17,6 +17,10 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
17
17
  ## Roadmap
18
18
 
19
19
  - [x] attachment file by file system
20
+ - [x] attachment file by buffer
21
+ - [x] attachment file by path
22
+ - [x] attachment file by url
23
+ - [x] attachment file by stream
20
24
  - [x] save meta data
21
25
  - [x] variantes
22
26
  - [x] images
@@ -0,0 +1,3 @@
1
+ import { Meta } from '../types/input.js';
2
+ export declare function metaFormBuffer(input: Buffer): Promise<Meta>;
3
+ export declare function metaFormFile(input: string, filename: string): Promise<Meta>;
@@ -0,0 +1,40 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
4
+ import mime from 'mime-types';
5
+ function getFileExtension(filename) {
6
+ if (!filename) {
7
+ return '';
8
+ }
9
+ const ext = path.extname(filename).slice(1);
10
+ return ext && /^[a-zA-Z0-9]+$/.test(ext) ? ext : '';
11
+ }
12
+ function metaByFileName(filename) {
13
+ return {
14
+ ext: getFileExtension(filename),
15
+ mime: mime.lookup(filename) || '',
16
+ };
17
+ }
18
+ export async function metaFormBuffer(input) {
19
+ const fileType = await fileTypeFromBuffer(input);
20
+ return {
21
+ extname: fileType?.ext || '',
22
+ mimeType: fileType?.mime || '',
23
+ size: input.length,
24
+ };
25
+ }
26
+ export async function metaFormFile(input, filename) {
27
+ let fileType;
28
+ let size = 0;
29
+ fileType = metaByFileName(filename);
30
+ if (fileType.ext === '' || fileType.mime === '') {
31
+ fileType = await fileTypeFromFile(input);
32
+ }
33
+ const stats = await fs.stat(input);
34
+ size = stats.size;
35
+ return {
36
+ extname: fileType?.ext || '',
37
+ mimeType: fileType?.mime || '',
38
+ size,
39
+ };
40
+ }
@@ -16,11 +16,11 @@ export declare class AttachmentManager<KnownConverters extends Record<string, Co
16
16
  constructor(config: ResolvedAttachmentConfig<KnownConverters>, drive: DriveService);
17
17
  getConfig(): ResolvedAttachmentConfig<KnownConverters>;
18
18
  createFromDbResponse(response: any): AttachmentType | null;
19
- createFromFile(file: MultipartFile): Promise<AttachmentType>;
20
- createFromPath(path: string, name?: string): Promise<AttachmentType>;
21
- createFromBuffer(buffer: Buffer, name?: string): Promise<AttachmentType>;
22
- createFromBase64(data: string, name?: string): Promise<AttachmentType>;
23
- createFromUrl(url: URL, name?: string): Promise<AttachmentType>;
19
+ createFromFile(input: MultipartFile): Promise<AttachmentType>;
20
+ createFromPath(input: string, name?: string): Promise<AttachmentType>;
21
+ createFromBuffer(input: Buffer, name?: string): Promise<AttachmentType>;
22
+ createFromBase64(input: string, name?: string): Promise<AttachmentType>;
23
+ createFromUrl(input: URL, name?: string): Promise<AttachmentType>;
24
24
  createFromStream(stream: NodeJS.ReadableStream, name?: string): Promise<AttachmentType>;
25
25
  getConverter(key: string): Promise<void | Converter>;
26
26
  computeUrl(attachment: AttachmentType | AttachmentBase, signedUrlOptions?: SignedURLOptions): Promise<void>;
@@ -4,11 +4,14 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
+ import path from 'node:path';
7
8
  import { DeferQueue } from '@poppinss/defer';
8
9
  import * as errors from './errors.js';
9
10
  import { Attachment } from './attachments/attachment.js';
10
- import { createAttachmentAttributes, downloadToTempFile, isBase64, streamToTempFile } from './utils/helpers.js';
11
+ import { downloadToTempFile, isBase64, streamToTempFile } from './utils/helpers.js';
11
12
  import ExifAdapter from './adapters/exif.js';
13
+ import { metaFormBuffer, metaFormFile } from './adapters/meta.js';
14
+ import { cuid } from '@adonisjs/core/helpers';
12
15
  const REQUIRED_ATTRIBUTES = ['name', 'size', 'extname', 'mimeType'];
13
16
  export class AttachmentManager {
14
17
  queue;
@@ -36,51 +39,60 @@ export class AttachmentManager {
36
39
  const attachment = new Attachment(this.#drive, attributes);
37
40
  return this.#configureAttachment(attachment);
38
41
  }
39
- async createFromFile(file) {
42
+ async createFromFile(input) {
40
43
  const attributes = {
41
- originalName: file.clientName,
42
- extname: file.extname,
43
- mimeType: `${file.type}/${file.subtype}`,
44
- size: file.size,
44
+ originalName: input.clientName,
45
+ extname: input.extname,
46
+ mimeType: `${input.type}/${input.subtype}`,
47
+ size: input.size,
45
48
  };
46
- if (!file.tmpPath) {
49
+ if (!input.tmpPath) {
47
50
  throw new errors.ENOENT();
48
51
  }
49
- const attachment = new Attachment(this.#drive, attributes, file.tmpPath);
52
+ const attachment = new Attachment(this.#drive, attributes, input.tmpPath);
50
53
  return this.#configureAttachment(attachment);
51
54
  }
52
- async createFromPath(path, name) {
53
- const attributes = await createAttachmentAttributes(path, name);
54
- const attachment = new Attachment(this.#drive, attributes, path);
55
+ async createFromPath(input, name) {
56
+ const meta = await metaFormFile(input, name || input);
57
+ if (meta.extname === '') {
58
+ meta.extname = 'tmp';
59
+ meta.mimeType = 'application/x-temp';
60
+ }
61
+ const attributes = {
62
+ ...meta,
63
+ originalName: name?.replace('tmp', meta.extname) || path.basename(input),
64
+ };
65
+ const attachment = new Attachment(this.#drive, attributes, input);
55
66
  return this.#configureAttachment(attachment);
56
67
  }
57
- async createFromBuffer(buffer, name) {
58
- if (!Buffer.isBuffer(buffer)) {
68
+ async createFromBuffer(input, name) {
69
+ if (!Buffer.isBuffer(input)) {
59
70
  throw new errors.E_ISNOT_BUFFER();
60
71
  }
61
- const attributes = await createAttachmentAttributes(buffer, name);
62
- const attachment = new Attachment(this.#drive, attributes, buffer);
72
+ const meta = await metaFormBuffer(input);
73
+ const ext = meta.extname || 'tmp';
74
+ const attributes = {
75
+ ...meta,
76
+ originalName: name || `${cuid()}.${ext}`,
77
+ };
78
+ const attachment = new Attachment(this.#drive, attributes, input);
63
79
  return this.#configureAttachment(attachment);
64
80
  }
65
- async createFromBase64(data, name) {
66
- const base64Data = data.replace(/^data:([A-Za-z-+\/]+);base64,/, '');
81
+ async createFromBase64(input, name) {
82
+ const base64Data = input.replace(/^data:([A-Za-z-+\/]+);base64,/, '');
67
83
  if (!isBase64(base64Data)) {
68
84
  throw new errors.E_ISNOT_BASE64();
69
85
  }
70
86
  const buffer = Buffer.from(base64Data, 'base64');
71
- return await this.createFromBuffer(buffer, name);
87
+ return this.createFromBuffer(buffer, name);
72
88
  }
73
- async createFromUrl(url, name) {
74
- const path = await downloadToTempFile(url);
75
- const attributes = await createAttachmentAttributes(path, name);
76
- const attachment = new Attachment(this.#drive, attributes, path);
77
- return this.#configureAttachment(attachment);
89
+ async createFromUrl(input, name) {
90
+ const tmpPath = await downloadToTempFile(input);
91
+ return this.createFromPath(tmpPath, name || path.basename(input.pathname));
78
92
  }
79
93
  async createFromStream(stream, name) {
80
- const path = await streamToTempFile(stream);
81
- const attributes = await createAttachmentAttributes(path, name);
82
- const attachment = new Attachment(this.#drive, attributes, path);
83
- return this.#configureAttachment(attachment);
94
+ const tmpPath = await streamToTempFile(stream);
95
+ return this.createFromPath(tmpPath, name || path.basename(tmpPath));
84
96
  }
85
97
  async getConverter(key) {
86
98
  if (this.#config.converters) {
@@ -130,8 +142,17 @@ export class AttachmentManager {
130
142
  await attachment.getDisk().delete(attachment.path);
131
143
  if (attachment instanceof Attachment) {
132
144
  if (attachment.variants) {
133
- const variantPath = attachment.variants[0].folder;
134
- await attachment.getDisk().deleteAll(variantPath);
145
+ if (attachment.options?.disk == 'fs') {
146
+ const variantPath = attachment.variants[0].folder;
147
+ await attachment.getDisk().deleteAll(variantPath);
148
+ }
149
+ else {
150
+ for (const key in attachment.variants) {
151
+ if (Object.prototype.hasOwnProperty.call(attachment.variants, key)) {
152
+ await attachment.getDisk().delete(attachment.variants[key].path);
153
+ }
154
+ }
155
+ }
135
156
  }
136
157
  }
137
158
  }
@@ -7,7 +7,7 @@
7
7
  import path from 'node:path';
8
8
  import { AttachmentBase } from './attachment_base.js';
9
9
  import { Variant } from './variant_attachment.js';
10
- import { createAttachmentAttributes } from '../utils/helpers.js';
10
+ import { metaFormBuffer, metaFormFile } from '../adapters/meta.js';
11
11
  export class Attachment extends AttachmentBase {
12
12
  originalName;
13
13
  variants;
@@ -35,8 +35,15 @@ export class Attachment extends AttachmentBase {
35
35
  * Methods
36
36
  */
37
37
  async createVariant(key, input) {
38
+ let meta;
39
+ if (Buffer.isBuffer(input)) {
40
+ meta = await metaFormBuffer(input);
41
+ }
42
+ else {
43
+ meta = await metaFormFile(input, this.name);
44
+ }
38
45
  const attributes = {
39
- ...(await createAttachmentAttributes(input)),
46
+ ...meta,
40
47
  key,
41
48
  folder: path.join(this.options.folder, 'variants', this.name),
42
49
  };
@@ -7,6 +7,9 @@
7
7
  import attachmentManager from '../../services/main.js';
8
8
  import { optionsSym } from '../utils/symbols.js';
9
9
  import { defaultOptionsDecorator } from '../utils/default_values.js';
10
+ import { afterFindHook, afterFetchHook, beforeSaveHook, afterSaveHook, beforeDeleteHook, } from '../utils/hooks.js';
11
+ import { clone } from '../utils/helpers.js';
12
+ import { defaultStateAttributeMixin } from '../utils/default_values.js';
10
13
  export const attachment = (options) => {
11
14
  return function (target, attributeName) {
12
15
  if (!target[optionsSym]) {
@@ -15,6 +18,28 @@ export const attachment = (options) => {
15
18
  target[optionsSym][attributeName] = options;
16
19
  const Model = target.constructor;
17
20
  Model.boot();
21
+ Model.$attachments = clone(defaultStateAttributeMixin);
22
+ /**
23
+ * Registering all hooks only once
24
+ */
25
+ if (!Model.$hooks.has('find', afterFindHook)) {
26
+ Model.after('find', afterFindHook);
27
+ }
28
+ if (!Model.$hooks.has('fetch', afterFetchHook)) {
29
+ Model.after('fetch', afterFetchHook);
30
+ }
31
+ if (!Model.$hooks.has('paginate', afterFetchHook)) {
32
+ Model.after('paginate', afterFetchHook);
33
+ }
34
+ if (!Model.$hooks.has('save', beforeSaveHook)) {
35
+ Model.before('save', beforeSaveHook);
36
+ }
37
+ if (!Model.$hooks.has('save', afterSaveHook)) {
38
+ Model.after('save', afterSaveHook);
39
+ }
40
+ if (!Model.$hooks.has('delete', beforeDeleteHook)) {
41
+ Model.before('delete', beforeDeleteHook);
42
+ }
18
43
  const { disk, folder, variants, meta, rename, ...columnOptions } = {
19
44
  ...defaultOptionsDecorator,
20
45
  ...options,
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * Unable to write file to the destination
9
9
  */
10
- export declare const E_CANNOT_WRITE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
10
+ export declare const E_CANNOT_WRITE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
11
11
  /**
12
12
  * Unable to read file
13
13
  */
@@ -15,56 +15,56 @@ export declare const E_CANNOT_READ_FILE: new (args: [key: string], options?: Err
15
15
  /**
16
16
  * Unable to read file
17
17
  */
18
- "@poppinss/utils/exception").Exception;
18
+ "@adonisjs/core/exceptions").Exception;
19
19
  /**
20
20
  * Unable to delete file
21
21
  */
22
- export declare const E_CANNOT_DELETE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
22
+ export declare const E_CANNOT_DELETE_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
23
23
  /**
24
24
  * Unable to set file visibility
25
25
  */
26
- export declare const E_CANNOT_SET_VISIBILITY: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
26
+ export declare const E_CANNOT_SET_VISIBILITY: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
27
27
  /**
28
28
  * Unable to generate URL for a file
29
29
  */
30
- export declare const E_CANNOT_GENERATE_URL: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
30
+ export declare const E_CANNOT_GENERATE_URL: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
31
31
  /**
32
32
  * Unable to generate temp file
33
33
  */
34
- export declare const E_CANNOT_GENERATE_TEMP_FILE: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
34
+ export declare const E_CANNOT_GENERATE_TEMP_FILE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
35
35
  /**
36
36
  * The file key has unallowed set of characters
37
37
  */
38
- export declare const E_UNALLOWED_CHARACTERS: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
38
+ export declare const E_UNALLOWED_CHARACTERS: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
39
39
  /**
40
40
  * Key post normalization leads to an empty string
41
41
  */
42
- export declare const E_INVALID_KEY: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
42
+ export declare const E_INVALID_KEY: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
43
43
  /**
44
44
  * Missing package
45
45
  */
46
- export declare const E_MISSING_PACKAGE: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
46
+ export declare const E_MISSING_PACKAGE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
47
47
  /**
48
48
  * Unable to create Attachment Object
49
49
  */
50
- export declare const E_CANNOT_CREATE_ATTACHMENT: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
50
+ export declare const E_CANNOT_CREATE_ATTACHMENT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
51
51
  /**
52
52
  * Unable to create variant
53
53
  */
54
- export declare const E_CANNOT_CREATE_VARIANT: new (args: [key: string], options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
54
+ export declare const E_CANNOT_CREATE_VARIANT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
55
55
  /**
56
56
  * Missing path
57
57
  */
58
- export declare const E_CANNOT_PATH_BY_CONVERTER: new (args?: any, options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
58
+ export declare const E_CANNOT_PATH_BY_CONVERTER: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
59
59
  /**
60
60
  * Is not a Buffer
61
61
  */
62
- export declare const E_ISNOT_BUFFER: new (args?: any, options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
62
+ export declare const E_ISNOT_BUFFER: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
63
63
  /**
64
64
  * Is not a Base64
65
65
  */
66
- export declare const E_ISNOT_BASE64: new (args?: any, options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
66
+ export declare const E_ISNOT_BASE64: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
67
67
  /**
68
68
  * Unable to read file
69
69
  */
70
- export declare const ENOENT: new (args?: any, options?: ErrorOptions) => import("@poppinss/utils/exception").Exception;
70
+ export declare const ENOENT: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;