@jrmc/adonis-attachment 2.2.0 → 2.3.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.
package/README.md CHANGED
@@ -8,6 +8,8 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
8
8
 
9
9
  [View documentation](https://adonis-attachment.jrmc.dev/)
10
10
 
11
+ [Discord](https://discord.gg/89eMn2vB)
12
+
11
13
  [ChangeLog](https://adonis-attachment.jrmc.dev/changelog.html)
12
14
 
13
15
  ⚠️ [Breaking change](https://adonis-attachment.jrmc.dev/changelog.html) version 2, include [@adonisjs/drive](https://docs.adonisjs.com/guides/digging-deeper/drive)
@@ -18,44 +20,61 @@ Project sample : [adonis-starter-kit](https://github.com/batosai/adonis-starter-
18
20
  - [x] save meta data
19
21
  - [x] variantes
20
22
  - [x] images
21
- - [ ] documents thumbnail
23
+ - [x] documents thumbnail
22
24
  - [x] videos thumbnail
23
25
  - [ ] command regenerate
26
+ - [ ] command make:convert
24
27
  - [x] adonis-drive/flydrive
25
28
  - [ ] jobs queue
26
29
  - [x] serialize
27
30
 
28
- ### Meta data list (if available)
29
31
 
30
- - extension name
31
- - size
32
- - dimension (width, height)
33
- - created date
34
- - orientation
35
- - mime type
36
- - gps
32
+ ## Setup
37
33
 
38
- ### Variants
34
+ Install and configure the package:
39
35
 
40
- Configure differents images sizes and formats
36
+ ```sh
37
+ node ace add @jrmc/adonis-attachment
38
+ ```
41
39
 
42
- ### Regenerate (coming soon)
40
+ ## Sample
43
41
 
44
- Regenerate variantes files
42
+ Simple upload file
45
43
 
46
- ### 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]
47
50
 
48
- 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
+ ```
49
56
 
50
- ### Jobs queue (coming soon)
57
+ ---
51
58
 
52
- Couple with a job queue (recommended, optional)
59
+ ```ts
60
+ // app/controllers/users_controller.ts
61
+ import { attachmentManager } from '@jrmc/adonis-attachment' // [!code focus]
53
62
 
54
- ## Setup
63
+ class UsersController {
64
+ public store({ request }: HttpContext) {
65
+ const avatar = request.file('avatar')! // [!code focus]
66
+ const user = new User()
55
67
 
56
- Install and configure the package:
68
+ user.avatar = await attachmentManager.createFromFile(avatar) // [!code focus]
69
+ await user.save()
70
+ }
71
+ }
72
+ ```
57
73
 
58
- ```sh
59
- node ace add @jrmc/adonis-attachment
74
+ ---
75
+
76
+ ```edge
77
+ <img src="{{ await user.avatar.getUrl() }}" loading="lazy" alt="" />
60
78
  ```
61
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
  }
@@ -8,7 +8,7 @@ import fs from 'node:fs/promises';
8
8
  import ExifReader from 'exifreader';
9
9
  import logger from '@adonisjs/core/services/logger';
10
10
  import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
11
- import { cleanObject, use } from '../utils/helpers.js';
11
+ import { bufferToTempFile, cleanObject, use } from '../utils/helpers.js';
12
12
  import { attachmentManager } from '../../index.js';
13
13
  export const exif = async (input) => {
14
14
  let fileType;
@@ -106,7 +106,11 @@ async function imageExif(buffer) {
106
106
  async function videoExif(input) {
107
107
  return new Promise(async (resolve) => {
108
108
  const ffmpeg = await use('fluent-ffmpeg');
109
- const ff = ffmpeg(input);
109
+ let file = input;
110
+ if (Buffer.isBuffer(input)) {
111
+ file = await bufferToTempFile(input);
112
+ }
113
+ const ff = ffmpeg(file);
110
114
  const config = attachmentManager.getConfig();
111
115
  if (config.bin) {
112
116
  if (config.bin.ffprobePath) {
@@ -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,11 +12,12 @@ 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>;
20
19
  createFromBuffer(buffer: Buffer, name?: string): Promise<Attachment>;
20
+ createFromBase64(data: string, name?: string): Promise<Attachment>;
21
21
  getConverter(key: string): Promise<void | Converter>;
22
22
  computeUrl(attachment: AttachmentType | AttachmentBase, signedUrlOptions?: SignedURLOptions): Promise<void>;
23
23
  preComputeUrl(attachment: AttachmentType): Promise<void>;
@@ -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,14 +39,25 @@ 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
  }
53
+ async createFromBase64(data, name) {
54
+ const base64Data = data.replace(/^data:([A-Za-z-+\/]+);base64,/, '');
55
+ if (!isBase64(base64Data)) {
56
+ throw new errors.E_ISNOT_BASE64();
57
+ }
58
+ const buffer = Buffer.from(base64Data, 'base64');
59
+ return await this.createFromBuffer(buffer, name);
60
+ }
52
61
  async getConverter(key) {
53
62
  if (this.#config.converters) {
54
63
  for (const c of this.#config.converters) {
@@ -89,47 +98,22 @@ export class AttachmentManager {
89
98
  else {
90
99
  attachment.meta = undefined;
91
100
  }
92
- try {
93
- if (Buffer.isBuffer(attachment.input)) {
94
- await attachment.getDisk().put(destinationPath, attachment.input);
95
- }
96
- else if (attachment.input) {
97
- await attachment.getDisk().copyFromFs(attachment.input, destinationPath);
98
- }
101
+ if (Buffer.isBuffer(attachment.input)) {
102
+ await attachment.getDisk().put(destinationPath, attachment.input);
99
103
  }
100
- catch (err) {
101
- this.#logger.error({ err }, 'Error send file');
104
+ else if (attachment.input) {
105
+ await attachment.getDisk().copyFromFs(attachment.input, destinationPath);
102
106
  }
103
107
  }
104
108
  async delete(attachment) {
105
109
  if (attachment.path) {
106
- try {
107
- const filePath = attachment.path;
108
- try {
109
- await attachment.getDisk().delete(filePath);
110
- }
111
- catch (accessError) {
112
- if (accessError.code === 'ENOENT') {
113
- this.#logger.warn(`File not found: ${filePath}`);
114
- }
115
- else {
116
- throw accessError;
117
- }
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);
118
116
  }
119
- if (attachment instanceof Attachment) {
120
- if (attachment.variants) {
121
- const variantPath = attachment.variants[0].folder;
122
- try {
123
- await attachment.getDisk().deleteAll(variantPath);
124
- }
125
- catch (rmError) {
126
- this.#logger.error(`Failed to remove variants folder: ${rmError.message}`);
127
- }
128
- }
129
- }
130
- }
131
- catch (error) {
132
- this.#logger.error(error);
133
117
  }
134
118
  }
135
119
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import type { ConverterAttributes } from '../types/converter.js';
8
+ import type { Input } from '../types/input.js';
9
+ import Converter from './converter.js';
10
+ export default class DocumentThumbnailConverter extends Converter {
11
+ handle({ input, options }: ConverterAttributes): Promise<any>;
12
+ documentToImage(LibreOfficeFileConverter: any, input: Input): Promise<any>;
13
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import logger from '@adonisjs/core/services/logger';
8
+ import Converter from './converter.js';
9
+ import ImageConverter from './image_converter.js';
10
+ import { use } from '../utils/helpers.js';
11
+ export default class DocumentThumbnailConverter extends Converter {
12
+ async handle({ input, options }) {
13
+ try {
14
+ const lib = await use('libreoffice-file-converter');
15
+ const LibreOfficeFileConverter = lib.LibreOfficeFileConverter;
16
+ const outputBuffer = await this.documentToImage(LibreOfficeFileConverter, input);
17
+ if (options && outputBuffer) {
18
+ const converter = new ImageConverter();
19
+ return await converter.handle({
20
+ input: outputBuffer,
21
+ options
22
+ });
23
+ }
24
+ return outputBuffer;
25
+ }
26
+ catch (err) {
27
+ logger.error({ err });
28
+ }
29
+ }
30
+ async documentToImage(LibreOfficeFileConverter, input) {
31
+ let binaryPaths = undefined;
32
+ if (this.binPaths && this.binPaths.libreofficePaths) {
33
+ binaryPaths = this.binPaths.libreofficePaths;
34
+ }
35
+ const libreOfficeFileConverter = new LibreOfficeFileConverter({
36
+ childProcessOptions: {
37
+ timeout: 60 * 1000,
38
+ },
39
+ binaryPaths
40
+ });
41
+ if (Buffer.isBuffer(input)) {
42
+ const output = await libreOfficeFileConverter.convert({
43
+ buffer: input,
44
+ input: 'buffer',
45
+ output: 'buffer',
46
+ format: 'jpeg',
47
+ });
48
+ return output;
49
+ }
50
+ else {
51
+ const output = await libreOfficeFileConverter.convert({
52
+ inputPath: input,
53
+ input: 'file',
54
+ output: 'buffer',
55
+ format: 'jpeg',
56
+ });
57
+ return output;
58
+ }
59
+ }
60
+ }
@@ -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
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import type { ConverterAttributes } from '../types/converter.js';
8
+ import type { Input } from '../types/input.js';
9
+ import Converter from './converter.js';
10
+ export default class PdfThumbnailConverter extends Converter {
11
+ handle({ input, options }: ConverterAttributes): Promise<any>;
12
+ pdfToImage(Poppler: any, input: Input): Promise<string>;
13
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @jrmc/adonis-attachment
3
+ *
4
+ * @license MIT
5
+ * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
+ */
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { cuid } from '@adonisjs/core/helpers';
10
+ import logger from '@adonisjs/core/services/logger';
11
+ import Converter from './converter.js';
12
+ import ImageConverter from './image_converter.js';
13
+ import { use } from '../utils/helpers.js';
14
+ export default class PdfThumbnailConverter extends Converter {
15
+ async handle({ input, options }) {
16
+ try {
17
+ const nodePoppler = await use('node-poppler');
18
+ const Poppler = nodePoppler.Poppler;
19
+ const filePath = await this.pdfToImage(Poppler, input);
20
+ if (options && filePath) {
21
+ const converter = new ImageConverter();
22
+ return await converter.handle({
23
+ input: filePath,
24
+ options
25
+ });
26
+ }
27
+ return filePath;
28
+ }
29
+ catch (err) {
30
+ logger.error({ err });
31
+ }
32
+ }
33
+ async pdfToImage(Poppler, input) {
34
+ let binPath = null;
35
+ if (this.binPaths && this.binPaths.pdftocairoBasePath) {
36
+ binPath = this.binPaths.pdftocairoBasePath;
37
+ }
38
+ const poppler = new Poppler(binPath);
39
+ const options = {
40
+ // firstPageToConvert: 1,
41
+ lastPageToConvert: 1,
42
+ jpegFile: true,
43
+ };
44
+ const filePath = path.join(os.tmpdir(), cuid());
45
+ await poppler.pdfToCairo(input, filePath, options);
46
+ return filePath + '-1.jpg';
47
+ }
48
+ }
@@ -5,8 +5,8 @@
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
7
  import type { ConverterAttributes } from '../types/converter.js';
8
+ import type { Input } from '../types/input.js';
8
9
  import Converter from './converter.js';
9
- import { Input } from '../types/input.js';
10
10
  export default class VideoThumbnailConvert extends Converter {
11
11
  handle({ input, options }: ConverterAttributes): Promise<any>;
12
12
  videoToImage(ffmpeg: Function, input: Input): Promise<string | false>;
@@ -7,13 +7,14 @@
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
- import { use } from '../utils/helpers.js';
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();
@@ -26,12 +27,19 @@ export default class VideoThumbnailConvert extends Converter {
26
27
  return filePath;
27
28
  }
28
29
  }
30
+ catch (err) {
31
+ logger.error({ err });
32
+ }
29
33
  }
30
34
  async videoToImage(ffmpeg, input) {
35
+ let file = input;
36
+ if (Buffer.isBuffer(input)) {
37
+ file = await bufferToTempFile(input);
38
+ }
31
39
  return new Promise((resolve) => {
32
40
  const folder = os.tmpdir();
33
41
  const filename = `${cuid()}.png`;
34
- const ff = ffmpeg(input);
42
+ const ff = ffmpeg(file);
35
43
  if (this.binPaths) {
36
44
  if (this.binPaths.ffmpegPath) {
37
45
  ff.setFfmpegPath(this.binPaths.ffmpegPath);
@@ -0,0 +1,62 @@
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(
27
+ /**
28
+ * Unable to read file
29
+ */
30
+ "@adonisjs/core/exceptions").Exception;
31
+ /**
32
+ * Unable to generate URL for a file
33
+ */
34
+ export declare const E_CANNOT_GENERATE_URL: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
35
+ /**
36
+ * The file key has unallowed set of characters
37
+ */
38
+ export declare const E_UNALLOWED_CHARACTERS: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
39
+ /**
40
+ * Key post normalization leads to an empty string
41
+ */
42
+ export declare const E_INVALID_KEY: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
43
+ /**
44
+ * Missing package
45
+ */
46
+ export declare const E_MISSING_PACKAGE: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
47
+ /**
48
+ * Unable to create Attachment Object
49
+ */
50
+ export declare const E_CANNOT_CREATE_ATTACHMENT: new (args: [key: string], options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
51
+ /**
52
+ * Is not a Buffer
53
+ */
54
+ export declare const E_ISNOT_BUFFER: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
55
+ /**
56
+ * Is not a Base64
57
+ */
58
+ export declare const E_ISNOT_BASE64: new (args?: any, options?: ErrorOptions) => import("@adonisjs/core/exceptions").Exception;
59
+ /**
60
+ * Unable to read file
61
+ */
62
+ 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: {
@@ -16,6 +16,8 @@ type ConverterConfig = {
16
16
  export type BinPaths = {
17
17
  ffmpegPath?: string;
18
18
  ffprobePath?: string;
19
+ pdftocairoBasePath?: string;
20
+ libreofficePaths?: Array<string>;
19
21
  };
20
22
  export type AttachmentConfig = {
21
23
  bin?: BinPaths;
@@ -18,3 +18,5 @@ export declare function createAttachmentAttributes(input: Input, name?: string):
18
18
  export declare function cleanObject(obj: any): any;
19
19
  export declare function clone(object: Object): any;
20
20
  export declare function use(module: string): Promise<any>;
21
+ export declare function bufferToTempFile(input: Buffer): Promise<string>;
22
+ export declare function isBase64(str: string): boolean;
@@ -4,11 +4,14 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import fs from 'fs/promises';
7
10
  import { cuid } from '@adonisjs/core/helpers';
8
11
  import string from '@adonisjs/core/helpers/string';
9
- import logger from '@adonisjs/core/services/logger';
10
12
  import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type';
11
13
  import { Attachment } from '../attachments/attachment.js';
14
+ import * as errors from '../errors.js';
12
15
  import { optionsSym } from './symbols.js';
13
16
  export function getAttachmentAttributeNames(modelInstance) {
14
17
  return Object.keys(modelInstance.$attributes).filter((attr) => modelInstance.$attributes[attr] instanceof Attachment);
@@ -61,9 +64,31 @@ export function clone(object) {
61
64
  export async function use(module) {
62
65
  try {
63
66
  const result = await import(module);
64
- return result.default;
67
+ if (result.default) {
68
+ return result.default;
69
+ }
70
+ return result;
71
+ }
72
+ catch (err) {
73
+ throw new errors.E_MISSING_PACKAGE([module], { cause: err });
74
+ }
75
+ }
76
+ export async function bufferToTempFile(input) {
77
+ const folder = os.tmpdir();
78
+ const tempFilePath = path.join(folder, `tempfile-${Date.now()}.tmp`);
79
+ await fs.writeFile(tempFilePath, input);
80
+ return tempFilePath;
81
+ }
82
+ export function isBase64(str) {
83
+ const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
84
+ if (!base64Regex.test(str)) {
85
+ return false;
86
+ }
87
+ try {
88
+ Buffer.from(str, 'base64').toString();
89
+ return true;
65
90
  }
66
- catch (error) {
67
- logger.error({ err: error }, `Dependence missing, please install ${module}`);
91
+ catch (err) {
92
+ return false;
68
93
  }
69
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrmc/adonis-attachment",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
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
  },
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * @jrmc/adonis-attachment
4
- *
5
- * @license MIT
6
- * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
7
- */
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * @jrmc/adonis-attachment
4
- *
5
- * @license MIT
6
- * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
7
- */
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * @jrmc/adonis-attachment
4
- *
5
- * @license MIT
6
- * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
7
- */
@@ -1,7 +0,0 @@
1
- export {};
2
- /**
3
- * @jrmc/adonis-attachment
4
- *
5
- * @license MIT
6
- * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
7
- */