@jrmc/adonis-attachment 5.0.0-beta.1 → 5.0.0-beta.3

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 (36) hide show
  1. package/build/index.d.ts +0 -1
  2. package/build/index.d.ts.map +1 -1
  3. package/build/index.js +0 -1
  4. package/build/providers/attachment_provider.d.ts.map +1 -1
  5. package/build/providers/attachment_provider.js +2 -1
  6. package/build/src/attachment_manager.d.ts +4 -2
  7. package/build/src/attachment_manager.d.ts.map +1 -1
  8. package/build/src/attachment_manager.js +3 -1
  9. package/build/src/attachments/attachment.d.ts +1 -1
  10. package/build/src/attachments/attachment.js +1 -1
  11. package/build/src/controllers/attachments_controller.d.ts.map +1 -1
  12. package/build/src/controllers/attachments_controller.js +74 -69
  13. package/build/src/decorators/attachment.d.ts +3 -3
  14. package/build/src/decorators/attachment.d.ts.map +1 -1
  15. package/build/src/services/record_with_attachment.d.ts.map +1 -1
  16. package/build/src/services/record_with_attachment.js +22 -9
  17. package/build/src/services/variant/variant_generator.d.ts +18 -0
  18. package/build/src/services/variant/variant_generator.d.ts.map +1 -0
  19. package/build/src/services/variant/variant_generator.js +91 -0
  20. package/build/src/services/variant/variant_persister.d.ts +17 -0
  21. package/build/src/services/variant/variant_persister.d.ts.map +1 -0
  22. package/build/src/services/variant/variant_persister.js +54 -0
  23. package/build/src/services/variant/variant_purger.d.ts +9 -0
  24. package/build/src/services/variant/variant_purger.d.ts.map +1 -0
  25. package/build/src/services/variant/variant_purger.js +42 -0
  26. package/build/src/services/variant_service.d.ts +7 -0
  27. package/build/src/services/variant_service.d.ts.map +1 -0
  28. package/build/src/services/variant_service.js +52 -0
  29. package/build/src/types/attachment.d.ts +3 -3
  30. package/build/src/types/attachment.d.ts.map +1 -1
  31. package/build/stubs/config.stub +102 -4
  32. package/build/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +4 -8
  34. package/build/src/converter_manager.d.ts +0 -13
  35. package/build/src/converter_manager.d.ts.map +0 -1
  36. package/build/src/converter_manager.js +0 -121
package/build/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import attachmentManager from './services/main.js';
2
2
  import RegenerateService from './services/regenerate_service.js';
3
3
  export { configure } from './configure.js';
4
- export { ConverterManager } from './src/converter_manager.js';
5
4
  export { Attachment } from './src/attachments/attachment.js';
6
5
  export { attachment } from './src/decorators/attachment.js';
7
6
  export { attachments } from './src/decorators/attachment.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,oBAAoB,CAAA;AAClD,OAAO,iBAAiB,MAAM,kCAAkC,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,MAAM,oBAAoB,CAAA;AAClD,OAAO,iBAAiB,MAAM,kCAAkC,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA"}
package/build/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import attachmentManager from './services/main.js';
2
2
  import RegenerateService from './services/regenerate_service.js';
3
3
  export { configure } from './configure.js';
4
- export { ConverterManager } from './src/converter_manager.js';
5
4
  export { Attachment } from './src/attachments/attachment.js';
6
5
  export { attachment } from './src/decorators/attachment.js';
7
6
  export { attachments } from './src/decorators/attachment.js';
@@ -1 +1 @@
1
- {"version":3,"file":"attachment_provider.d.ts","sourceRoot":"","sources":["../../providers/attachment_provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAIhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAiB,iBAAiB;QAChC,iBAAiB,EAAE,iBAAiB,CAAA;KACrC;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,KAAK,CAAA;KACzC;CACF;AAED,MAAM,CAAC,OAAO,OAAO,kBAAkB;;IAGzB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,QAAQ;IAoBF,IAAI;CAQX"}
1
+ {"version":3,"file":"attachment_provider.d.ts","sourceRoot":"","sources":["../../providers/attachment_provider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAIhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAiB,iBAAiB;QAChC,iBAAiB,EAAE,iBAAiB,CAAA;KACrC;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,KAAK,CAAA;KACzC;CACF;AAED,MAAM,CAAC,OAAO,OAAO,kBAAkB;;IAGzB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,QAAQ;IAqBF,IAAI;CAQX"}
@@ -18,10 +18,11 @@ export default class AttachmentProvider {
18
18
  const attachmentConfig = this.app.config.get('attachment');
19
19
  const config = await configProvider.resolve(this.app, attachmentConfig);
20
20
  const drive = await this.app.container.make('drive.manager');
21
+ const lock = await this.app.container.make('lock.manager');
21
22
  if (!config) {
22
23
  throw new RuntimeException('Invalid config exported from "config/attachment.ts" file. Make sure to use the defineConfig method');
23
24
  }
24
- this.#manager = new AttachmentManager(config, drive);
25
+ this.#manager = new AttachmentManager(config, drive, lock);
25
26
  return this.#manager;
26
27
  });
27
28
  }
@@ -5,6 +5,7 @@
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
7
  import type { DriveService, SignedURLOptions } from '@adonisjs/drive/types';
8
+ import type { LockService } from '@adonisjs/lock/types';
8
9
  import type { MultipartFile } from '@adonisjs/core/bodyparser';
9
10
  import type { AttachmentAttributes, AttachmentBase, Attachment as AttachmentType } from './types/attachment.js';
10
11
  import type { ResolvedAttachmentConfig } from './define_config.js';
@@ -13,14 +14,15 @@ import Converter from './converters/converter.js';
13
14
  export declare class AttachmentManager<KnownConverters extends Record<string, Converter>> {
14
15
  #private;
15
16
  queue: DeferQueue;
16
- constructor(config: ResolvedAttachmentConfig<KnownConverters>, drive: DriveService);
17
+ lock: LockService;
18
+ constructor(config: ResolvedAttachmentConfig<KnownConverters>, drive: DriveService, lock: LockService);
17
19
  createFromDbResponse(response?: string | JSON): AttachmentType | null;
18
20
  createFromFile(input: MultipartFile): Promise<AttachmentType>;
19
21
  createFromFiles(inputs: MultipartFile[]): Promise<(AttachmentBase & {
20
22
  originalName: string;
21
23
  variants?: import("./types/attachment.js").Variant[];
22
24
  createVariant(key: string, input: import("./types/input.js").Input): Promise<import("./types/attachment.js").Variant>;
23
- getVariant(variantName: string): import("./types/attachment.js").Variant | undefined;
25
+ getVariant(variantName: string): import("./types/attachment.js").Variant | null;
24
26
  getUrl(variantName?: string): Promise<string>;
25
27
  getSignedUrl(variantNameOrOptions?: string | SignedURLOptions, signedUrlOptions?: SignedURLOptions): Promise<string>;
26
28
  toObject(): AttachmentAttributes;
@@ -1 +1 @@
1
- {"version":3,"file":"attachment_manager.d.ts","sourceRoot":"","sources":["../../src/attachment_manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,IAAI,cAAc,EAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,SAAS,MAAM,2BAA2B,CAAA;AAQjD,qBAAa,iBAAiB,CAAC,eAAe,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;;IAC9E,KAAK,aAAA;gBAIO,MAAM,EAAE,wBAAwB,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,YAAY;IASlF,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAiBvC,cAAc,CAAC,KAAK,EAAE,aAAa;IAgBnC,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE;;;;;;;;;IAIvC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAiB3C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAgB7C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAW7C,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM;IAMvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM;IAM7D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAMpD,UAAU,CACd,UAAU,EAAE,cAAc,GAAG,cAAc,EAC3C,gBAAgB,CAAC,EAAE,gBAAgB;IAY/B,aAAa,CAAC,UAAU,EAAE,cAAc;IAgBxC,KAAK,CAAC,UAAU,EAAE,cAAc;IAgBhC,MAAM,CAAC,UAAU,EAAE,cAAc;IAuBvC,SAAS;CAqBV"}
1
+ {"version":3,"file":"attachment_manager.d.ts","sourceRoot":"","sources":["../../src/attachment_manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,IAAI,cAAc,EAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,SAAS,MAAM,2BAA2B,CAAA;AAQjD,qBAAa,iBAAiB,CAAC,eAAe,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;;IAC9E,KAAK,aAAA;IAGL,IAAI,EAAE,WAAW,CAAA;gBAEL,MAAM,EAAE,wBAAwB,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW;IAUrG,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAiBvC,cAAc,CAAC,KAAK,EAAE,aAAa;IAgBnC,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE;;;;;;;;;IAIvC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAiB3C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAgB7C,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAW7C,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM;IAMvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM;IAM7D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAMpD,UAAU,CACd,UAAU,EAAE,cAAc,GAAG,cAAc,EAC3C,gBAAgB,CAAC,EAAE,gBAAgB;IAY/B,aAAa,CAAC,UAAU,EAAE,cAAc;IAgBxC,KAAK,CAAC,UAAU,EAAE,cAAc;IAgBhC,MAAM,CAAC,UAAU,EAAE,cAAc;IAuBvC,SAAS;CAqBV"}
@@ -17,8 +17,10 @@ export class AttachmentManager {
17
17
  queue;
18
18
  #config;
19
19
  #drive;
20
- constructor(config, drive) {
20
+ lock;
21
+ constructor(config, drive, lock) {
21
22
  this.#drive = drive;
23
+ this.lock = lock;
22
24
  this.#config = config;
23
25
  const concurrency = this.#config.queue?.concurrency || 1;
24
26
  this.queue = new DeferQueue({ concurrency });
@@ -21,7 +21,7 @@ export declare class Attachment extends AttachmentBase implements AttachmentInte
21
21
  * Methods
22
22
  */
23
23
  createVariant(key: string, input: Input): Promise<Variant>;
24
- getVariant(variantName: string): Variant | undefined;
24
+ getVariant(variantName: string): Variant | null;
25
25
  getUrl(variantName?: string): Promise<string>;
26
26
  getSignedUrl(variantNameOrOptions?: string | SignedURLOptions, signedUrlOptions?: SignedURLOptions): Promise<string>;
27
27
  setOptions(options: LucidOptions): this;
@@ -56,7 +56,7 @@ export class Attachment extends AttachmentBase {
56
56
  return variant;
57
57
  }
58
58
  getVariant(variantName) {
59
- return this.variants?.find((v) => v.key === variantName);
59
+ return this.variants?.find((v) => v.key === variantName) ?? null;
60
60
  }
61
61
  async getUrl(variantName) {
62
62
  if (variantName) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachments_controller.d.ts","sourceRoot":"","sources":["../../../src/controllers/attachments_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAkBtD,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAElC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW;CA2FhD"}
1
+ {"version":3,"file":"attachments_controller.d.ts","sourceRoot":"","sources":["../../../src/controllers/attachments_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAoBtD,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAElC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW;CAgGhD"}
@@ -1,87 +1,92 @@
1
1
  import path from 'node:path';
2
+ import { Readable } from 'node:stream';
2
3
  import encryption from '@adonisjs/core/services/encryption';
3
4
  import db from '@adonisjs/lucid/services/db';
4
5
  import { attachmentManager } from '@jrmc/adonis-attachment';
5
- import { ConverterManager } from '../converter_manager.js';
6
- import { Readable } from 'node:stream';
6
+ import VariantGenerator from '../services/variant/variant_generator.js';
7
+ import VariantPersister from '../services/variant/variant_persister.js';
7
8
  export default class AttachmentsController {
8
9
  async handle({ request, response }) {
9
10
  const { key } = request.params();
10
11
  const format = request.qs()?.variant;
11
- const index = request.qs()?.index;
12
- let isAttachments = false;
12
+ let multiple = false;
13
13
  const data = encryption.decrypt(key);
14
- const queryWithTableSelection = await db
15
- .from(data.model)
16
- .select(data.attribute)
17
- .where('id', data.id).first();
18
- /*
19
- * 1. Get the entity
20
- */
21
- let result = JSON.parse(queryWithTableSelection[data.attribute]);
22
- if (Array.isArray(result)) {
23
- isAttachments = true;
24
- result = result[index || 0];
25
- }
26
- result.folder = path.dirname(result.path);
27
- /*
28
- * 2. Get the attachment
29
- */
30
- const attachment = attachmentManager.createFromDbResponse(result);
31
- attachment?.setOptions(data?.options);
32
- if (!attachment) {
33
- return response.notFound();
34
- }
35
- /*
36
- * 4. Get the variant
37
- */
38
- const variant = attachment?.getVariant(format);
39
- /*
40
- * 5. Get the stream
41
- * if variant and path, get the stream and return it
42
- * if not, generate the variant
43
- * if not, return the default file
44
- */
45
- if (variant && variant?.path) {
46
- const image = await variant.getStream();
47
- const readable = Readable.from(image);
48
- response.header('Content-Type', variant?.mimeType);
49
- response.stream(readable);
50
- }
51
- else {
52
- let attachmentOrAttachmentsString;
53
- const converter = (await attachmentManager.getConverter(format));
54
- const variant = await ConverterManager.generate({
55
- key: format,
56
- attachment,
57
- converter
58
- });
59
- if (isAttachments) {
60
- attachmentOrAttachmentsString = JSON.stringify([attachment.toObject()]);
14
+ await attachmentManager.lock.createLock(`attachment.${data.model}-${data.attribute}`).run(async () => {
15
+ const queryWithTableSelection = await db
16
+ .from(data.model)
17
+ .select(data.attribute)
18
+ .where('id', data.id).first();
19
+ /*
20
+ * 1. Get the Attachment(s)
21
+ */
22
+ const result = JSON.parse(queryWithTableSelection[data.attribute]);
23
+ const attachments = [];
24
+ let currentAttachment = null;
25
+ if (Array.isArray(result)) {
26
+ multiple = true;
27
+ for (const item of result) {
28
+ item.folder = path.dirname(item.path);
29
+ const attachment = attachmentManager.createFromDbResponse(item);
30
+ if (attachment) {
31
+ attachment.setOptions(data.options);
32
+ attachments.push(attachment);
33
+ }
34
+ }
35
+ currentAttachment = attachments[data.index || 0];
61
36
  }
62
37
  else {
63
- attachmentOrAttachmentsString = JSON.stringify(attachment.toObject());
38
+ result.folder = path.dirname(result.path);
39
+ currentAttachment = attachmentManager.createFromDbResponse(result);
40
+ if (currentAttachment) {
41
+ currentAttachment.setOptions(data.options);
42
+ }
64
43
  }
65
- const trx = await db.transaction();
66
- // trx.after('rollback', rollback)
67
- try {
68
- await trx.query().from(data.model).where('id', data.id).update({
69
- [data.attribute]: attachmentOrAttachmentsString
44
+ if (!currentAttachment) {
45
+ return response.notFound();
46
+ }
47
+ /*
48
+ * 2. Get the variant
49
+ */
50
+ let variant = currentAttachment.getVariant(format);
51
+ /*
52
+ * 3. Get the stream
53
+ * if variant and path, get the stream and return it
54
+ * if not, generate the variant
55
+ * if not, return the default file
56
+ */
57
+ if (!variant && format) {
58
+ const converter = (await attachmentManager.getConverter(format));
59
+ variant = await (new VariantGenerator()).generateVariant({
60
+ key: format,
61
+ attachment: currentAttachment,
62
+ converter
70
63
  });
71
- await trx.commit();
64
+ if (variant) {
65
+ const variantPersister = new VariantPersister({
66
+ id: data.id,
67
+ modelTable: data.model,
68
+ attributeName: data.attribute,
69
+ multiple
70
+ });
71
+ await variantPersister.persist({ attachments: attachments ?? [currentAttachment], variants: [variant] });
72
+ }
72
73
  }
73
- catch (error) {
74
- await trx.rollback();
74
+ /*
75
+ * 5. Get the stream
76
+ */
77
+ let file;
78
+ let mimeType;
79
+ if (variant) {
80
+ file = await variant.getStream();
81
+ mimeType = variant.mimeType;
75
82
  }
76
- finally {
77
- if (!variant) {
78
- return response.notFound();
79
- }
80
- const image = await variant.getStream();
81
- const readable = Readable.from(image);
82
- response.header('Content-Type', variant.mimeType);
83
- response.stream(readable);
83
+ else {
84
+ file = await currentAttachment.getStream();
85
+ mimeType = currentAttachment.mimeType;
84
86
  }
85
- }
87
+ const readable = Readable.from(file);
88
+ response.header('Content-Type', mimeType);
89
+ response.stream(readable);
90
+ });
86
91
  }
87
92
  }
@@ -4,12 +4,12 @@
4
4
  * @license MIT
5
5
  * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
6
6
  */
7
- import type { LucidModel } from '@adonisjs/lucid/types/model';
7
+ import type { LucidModel, LucidRow } from '@adonisjs/lucid/types/model';
8
8
  import type { LucidOptions } from '../types/attachment.js';
9
9
  import type { AttributeOfRowWithAttachment } from '../types/mixin.js';
10
10
  export declare const bootModel: (model: LucidModel & {
11
11
  $attachments: AttributeOfRowWithAttachment;
12
12
  }) => void;
13
- export declare const attachment: (options?: LucidOptions) => (target: any, attributeName: string) => void;
14
- export declare const attachments: (options?: LucidOptions) => (target: any, attributeName: string) => void;
13
+ export declare const attachment: <T = LucidRow>(options?: LucidOptions<T>) => (target: any, attributeName: string) => void;
14
+ export declare const attachments: <T = LucidRow>(options?: LucidOptions<T>) => (target: any, attributeName: string) => void;
15
15
  //# sourceMappingURL=attachment.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../../src/decorators/attachment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAKtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAA;AAYrE,eAAO,MAAM,SAAS,UACb,UAAU,GAAG;IAClB,YAAY,EAAE,4BAA4B,CAAA;CAC3C,SA0BF,CAAA;AAyDD,eAAO,MAAM,UAAU,aAtBkD,YAAY,cACxD,GAAG,iBAAiB,MAAM,SAqBJ,CAAA;AACnD,eAAO,MAAM,WAAW,aAvBiD,YAAY,cACxD,GAAG,iBAAiB,MAAM,SAiCpD,CAAA"}
1
+ {"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../../src/decorators/attachment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACvE,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAKtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAA;AAYrE,eAAO,MAAM,SAAS,UACb,UAAU,GAAG;IAClB,YAAY,EAAE,4BAA4B,CAAA;CAC3C,SA0BF,CAAA;AAyDD,eAAO,MAAM,UAAU,GAtBwC,CAAC,uBAAuB,YAAY,CAAC,CAAC,CAAC,cACzE,GAAG,iBAAiB,MAAM,SAqBJ,CAAA;AACnD,eAAO,MAAM,WAAW,GAvBuC,CAAC,uBAAuB,YAAY,CAAC,CAAC,CAAC,cACzE,GAAG,iBAAiB,MAAM,SAiCpD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"record_with_attachment.d.ts","sourceRoot":"","sources":["../../../src/services/record_with_attachment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAgB,MAAM,wBAAwB,CAAA;AACxF,OAAO,KAAK,EAAE,oBAAoB,IAAI,kCAAkC,EAAE,MAAM,qBAAqB,CAAA;AACrG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAW/D,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,kCAAkC;;gBAGzE,GAAG,EAAE,iBAAiB;IAWlC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDxB,WAAW,CAAC,OAAO;;KAA4B,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BzB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BjC,kBAAkB,CAAC,OAAO,GAAE,iBAAsB;IAmClD,MAAM;IA2CN,SAAS;IAkBf,IAAI,GAAG,sBAEN;IAED,cAAc,CAAC,OAAO,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE;CAwEvG"}
1
+ {"version":3,"file":"record_with_attachment.d.ts","sourceRoot":"","sources":["../../../src/services/record_with_attachment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAgB,MAAM,wBAAwB,CAAA;AACxF,OAAO,KAAK,EAAE,oBAAoB,IAAI,kCAAkC,EAAE,MAAM,qBAAqB,CAAA;AACrG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAW/D,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,kCAAkC;;gBAGzE,GAAG,EAAE,iBAAiB;IAWlC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDxB,WAAW,CAAC,OAAO;;KAA4B,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BzB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqCjC,kBAAkB,CAAC,OAAO,GAAE,iBAAsB;IAuClD,MAAM;IA2CN,SAAS;IAkBf,IAAI,GAAG,sBAEN;IAED,cAAc,CAAC,OAAO,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE;CA4EvG"}
@@ -1,10 +1,10 @@
1
1
  import logger from '@adonisjs/core/services/logger';
2
2
  import encryption from '@adonisjs/core/services/encryption';
3
3
  import attachmentManager from '../../services/main.js';
4
+ import VariantService from './variant_service.js';
4
5
  import { defaultStateAttributeMixin } from '../utils/default_values.js';
5
6
  import { Attachment } from '../attachments/attachment.js';
6
7
  import { optionsSym } from '../utils/symbols.js';
7
- import { ConverterManager } from '../converter_manager.js';
8
8
  import { E_CANNOT_CREATE_VARIANT } from '../errors.js';
9
9
  export default class RecordWithAttachment {
10
10
  #row;
@@ -113,6 +113,7 @@ export default class RecordWithAttachment {
113
113
  model: model.table,
114
114
  id: this.#row.$attributes['id'],
115
115
  attribute: name,
116
+ index: i,
116
117
  options: {
117
118
  disk,
118
119
  folder,
@@ -133,16 +134,21 @@ export default class RecordWithAttachment {
133
134
  * Launch async generation variants
134
135
  */
135
136
  for await (const name of attachmentAttributeNames) {
137
+ if (!this.#row.$attributes[name]) {
138
+ continue;
139
+ }
136
140
  const record = this;
137
141
  attachmentManager.queue.push({
138
142
  name: `${this.#row.constructor.name}-${name}`,
139
143
  async run() {
140
- const converterManager = new ConverterManager({
141
- record,
142
- attributeName: name,
143
- options: record.#getOptionsByAttributeName(name),
144
+ await attachmentManager.lock.createLock(`attachment.${record.#row.constructor.name}-${name}`).run(async () => {
145
+ const variantService = new VariantService({
146
+ record,
147
+ attributeName: name,
148
+ options: record.#getOptionsByAttributeName(name),
149
+ });
150
+ await variantService.run();
144
151
  });
145
- await converterManager.run();
146
152
  },
147
153
  })
148
154
  .onError = function (error) {
@@ -164,11 +170,14 @@ export default class RecordWithAttachment {
164
170
  attachmentAttributeNames = this.#getAttributeNamesOfAttachment();
165
171
  }
166
172
  for await (const name of attachmentAttributeNames) {
173
+ if (!this.#row.$attributes[name]) {
174
+ continue;
175
+ }
167
176
  const record = this;
168
177
  attachmentManager.queue.push({
169
178
  name: `${this.#row.constructor.name}-${name}`,
170
179
  async run() {
171
- const converterManager = new ConverterManager({
180
+ const variantService = new VariantService({
172
181
  record,
173
182
  attributeName: name,
174
183
  options: record.#getOptionsByAttributeName(name),
@@ -176,7 +185,7 @@ export default class RecordWithAttachment {
176
185
  variants: options.variants
177
186
  }
178
187
  });
179
- await converterManager.run();
188
+ await variantService.run();
180
189
  },
181
190
  })
182
191
  .onError = function (error) {
@@ -254,7 +263,11 @@ export default class RecordWithAttachment {
254
263
  attachments = this.#getAttachmentsByAttributeName(options.attributeName);
255
264
  }
256
265
  const opts = this.#getOptionsByAttributeName(options.attributeName);
257
- attachments.map((attachment) => attachment.setOptions(opts).makeFolder(this.#row));
266
+ attachments.forEach((attachment) => {
267
+ if (attachment) {
268
+ attachment.setOptions(opts).makeFolder(this.#row);
269
+ }
270
+ });
258
271
  return attachments;
259
272
  }
260
273
  #getAttachmentsByAttributeName(name) {
@@ -0,0 +1,18 @@
1
+ import type { Attachment, Variant, LucidOptions } from '../../types/attachment.js';
2
+ import type { Converter } from '../../types/converter.js';
3
+ export default class VariantGenerator {
4
+ #private;
5
+ generate({ attachments, options, filters }: {
6
+ attachments: Attachment[];
7
+ options: LucidOptions;
8
+ filters?: {
9
+ variants?: string[];
10
+ };
11
+ }): Promise<Variant[]>;
12
+ generateVariant({ key, attachment, converter }: {
13
+ key: string;
14
+ attachment: Attachment;
15
+ converter: Converter;
16
+ }): Promise<Variant | null>;
17
+ }
18
+ //# sourceMappingURL=variant_generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant_generator.d.ts","sourceRoot":"","sources":["../../../../src/services/variant/variant_generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAOzD,MAAM,CAAC,OAAO,OAAO,gBAAgB;;IAC7B,QAAQ,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;QAChD,WAAW,EAAE,UAAU,EAAE,CAAC;QAC1B,OAAO,EAAE,YAAY,CAAC;QACtB,OAAO,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAA;KAClC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAmBhB,eAAe,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE;QACpD,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,UAAU,CAAC;QACvB,SAAS,EAAE,SAAS,CAAA;KACrB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAkF5B"}
@@ -0,0 +1,91 @@
1
+ import logger from '@adonisjs/core/services/logger';
2
+ import attachmentManager from '../../../services/main.js';
3
+ import { streamToTempFile } from '../../utils/helpers.js';
4
+ export default class VariantGenerator {
5
+ async generate({ attachments, options, filters }) {
6
+ const variants = [];
7
+ const variantKeys = this.#getVariantKeysToProcess(options, filters);
8
+ for (const key of variantKeys) {
9
+ const converter = await this.#getConverter(key);
10
+ if (!converter)
11
+ continue;
12
+ for (const attachment of attachments) {
13
+ const variant = await this.generateVariant({ key, attachment, converter });
14
+ if (variant) {
15
+ variants.push(variant);
16
+ }
17
+ }
18
+ }
19
+ return variants;
20
+ }
21
+ async generateVariant({ key, attachment, converter }) {
22
+ try {
23
+ const input = await this.#prepareInput(attachment);
24
+ const output = await this.#convertFile(input, converter);
25
+ if (!output) {
26
+ // throw new errors.E_CANNOT_PATH_BY_CONVERTER()
27
+ logger.warn(`Converter returned no output for key: ${key}`);
28
+ return null;
29
+ }
30
+ const variant = await attachment.createVariant(key, output);
31
+ await this.#processBlurhash(variant, converter);
32
+ await attachmentManager.write(variant);
33
+ return variant;
34
+ }
35
+ catch (error) {
36
+ logger.error(`Failed to generate variant ${key} for attachment: ${error.message}`);
37
+ return null;
38
+ }
39
+ }
40
+ #getVariantKeysToProcess(options, filters) {
41
+ if (!options.variants)
42
+ return [];
43
+ return options.variants.filter(key => filters?.variants === undefined ||
44
+ filters.variants.includes(key));
45
+ }
46
+ async #getConverter(key) {
47
+ try {
48
+ return await attachmentManager.getConverter(key);
49
+ }
50
+ catch (error) {
51
+ logger.error(`Failed to get converter for key ${key}: ${error.message}`);
52
+ return null;
53
+ }
54
+ }
55
+ async #prepareInput(attachment) {
56
+ if (attachment.input) {
57
+ return attachment.input;
58
+ }
59
+ const stream = await attachment.getStream();
60
+ return await streamToTempFile(stream);
61
+ }
62
+ async #convertFile(input, converter) {
63
+ if (!converter.handle) {
64
+ throw new Error('Converter handle method is required');
65
+ }
66
+ if (!converter.options) {
67
+ throw new Error('Converter options are required');
68
+ }
69
+ return await converter.handle({
70
+ input,
71
+ options: converter.options,
72
+ });
73
+ }
74
+ async #processBlurhash(variant, converter) {
75
+ const blurhashConfig = converter.options?.blurhash;
76
+ if (!blurhashConfig)
77
+ return;
78
+ const shouldGenerate = typeof blurhashConfig === 'boolean'
79
+ ? blurhashConfig
80
+ : blurhashConfig.enabled === true;
81
+ if (!shouldGenerate)
82
+ return;
83
+ try {
84
+ const options = typeof blurhashConfig !== 'boolean' ? blurhashConfig : undefined;
85
+ await variant.generateBlurhash(options);
86
+ }
87
+ catch (error) {
88
+ logger.error(`Blurhash generation failed: ${error.message}`);
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,17 @@
1
+ import type { Attachment, Variant } from '../../types/attachment.js';
2
+ type PersistAttributes = {
3
+ id: string;
4
+ modelTable: string;
5
+ attributeName: string;
6
+ multiple: boolean;
7
+ };
8
+ export default class VariantPersister {
9
+ #private;
10
+ constructor({ id, modelTable, attributeName, multiple }: PersistAttributes);
11
+ persist({ attachments, variants }: {
12
+ attachments: Attachment[];
13
+ variants: Variant[];
14
+ }): Promise<void>;
15
+ }
16
+ export {};
17
+ //# sourceMappingURL=variant_persister.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant_persister.d.ts","sourceRoot":"","sources":["../../../../src/services/variant/variant_persister.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAOpE,KAAK,iBAAiB,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,gBAAgB;;gBAMvB,EAAE,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,iBAAiB;IAOpE,OAAO,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE;QACvC,WAAW,EAAE,UAAU,EAAE,CAAC;QAC1B,QAAQ,EAAE,OAAO,EAAE,CAAA;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;CA2ClB"}
@@ -0,0 +1,54 @@
1
+ import logger from '@adonisjs/core/services/logger';
2
+ import string from '@adonisjs/core/helpers/string';
3
+ import db from '@adonisjs/lucid/services/db';
4
+ import attachmentManager from '../../../services/main.js';
5
+ export default class VariantPersister {
6
+ #id;
7
+ #modelTable;
8
+ #attributeName;
9
+ #multiple;
10
+ constructor({ id, modelTable, attributeName, multiple }) {
11
+ this.#id = id;
12
+ this.#modelTable = modelTable;
13
+ this.#attributeName = attributeName;
14
+ this.#multiple = multiple;
15
+ }
16
+ async persist({ attachments, variants }) {
17
+ const rollback = () => this.#rollbackVariants(variants);
18
+ const trx = await db.transaction();
19
+ trx.after('rollback', rollback);
20
+ try {
21
+ const data = this.#prepareUpdateData(attachments);
22
+ await this.#executeUpdate(trx, data);
23
+ await trx.commit();
24
+ }
25
+ catch (error) {
26
+ logger.error(`Persist failed: ${error.message}`);
27
+ await trx.rollback();
28
+ throw error;
29
+ }
30
+ }
31
+ #rollbackVariants(variants) {
32
+ variants.forEach(variant => {
33
+ try {
34
+ attachmentManager.remove(variant);
35
+ }
36
+ catch (error) {
37
+ logger.error(`Rollback failed for variant: ${error.message}`);
38
+ }
39
+ });
40
+ }
41
+ #prepareUpdateData(attachments) {
42
+ const index = string.snakeCase(this.#attributeName);
43
+ const data = this.#multiple
44
+ ? attachments.map(att => att.toObject())
45
+ : attachments[0]?.toObject();
46
+ return { [index]: JSON.stringify(data) };
47
+ }
48
+ async #executeUpdate(trx, data) {
49
+ await trx.query()
50
+ .from(this.#modelTable)
51
+ .where('id', this.#id)
52
+ .update(data);
53
+ }
54
+ }
@@ -0,0 +1,9 @@
1
+ import type { Attachment } from '../../types/attachment.js';
2
+ export default class VariantPurger {
3
+ #private;
4
+ constructor(filters?: {
5
+ variants?: string[];
6
+ });
7
+ purge(attachments: Attachment[]): Promise<void>;
8
+ }
9
+ //# sourceMappingURL=variant_purger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant_purger.d.ts","sourceRoot":"","sources":["../../../../src/services/variant/variant_purger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,2BAA2B,CAAA;AAIpE,MAAM,CAAC,OAAO,OAAO,aAAa;;gBAGpB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE;IAIvC,KAAK,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CA6CtD"}