@jrmc/adonis-attachment 3.1.0 → 3.2.0
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 +4 -0
- package/build/src/adapters/meta.d.ts +3 -0
- package/build/src/adapters/meta.js +40 -0
- package/build/src/attachment_manager.d.ts +5 -5
- package/build/src/attachment_manager.js +39 -27
- package/build/src/attachments/attachment.js +9 -2
- package/build/src/decorators/attachment.js +25 -0
- package/build/src/errors.d.ts +15 -15
- package/build/src/mixins/attachmentable.d.ts +5 -314
- package/build/src/mixins/attachmentable.js +10 -88
- package/build/src/types/converter.d.ts +22 -1
- package/build/src/types/input.d.ts +5 -0
- package/build/src/utils/helpers.d.ts +0 -7
- package/build/src/utils/helpers.js +4 -26
- package/build/src/utils/hooks.d.ts +11 -0
- package/build/src/utils/hooks.js +92 -0
- package/package.json +18 -16
- package/build/bin/test.d.ts +0 -1
- package/build/bin/test.js +0 -34
- package/build/tests/attachment-manager.spec.d.ts +0 -7
- package/build/tests/attachment-manager.spec.js +0 -234
- package/build/tests/attachment.spec.d.ts +0 -7
- package/build/tests/attachment.spec.js +0 -16
- package/build/tests/commands.spec.d.ts +0 -7
- package/build/tests/commands.spec.js +0 -58
- package/build/tests/fixtures/converters/image_converter.d.ts +0 -12
- package/build/tests/fixtures/converters/image_converter.js +0 -12
- package/build/tests/fixtures/factories/user.d.ts +0 -8
- package/build/tests/fixtures/factories/user.js +0 -19
- package/build/tests/fixtures/factories/user_with_variants.d.ts +0 -8
- package/build/tests/fixtures/factories/user_with_variants.js +0 -19
- package/build/tests/fixtures/migrations/create_users_table.d.ts +0 -12
- package/build/tests/fixtures/migrations/create_users_table.js +0 -23
- package/build/tests/fixtures/models/user.d.ts +0 -466
- package/build/tests/fixtures/models/user.js +0 -36
- package/build/tests/fixtures/models/user_with_variants.d.ts +0 -465
- package/build/tests/fixtures/models/user_with_variants.js +0 -33
- package/build/tests/helpers/app.d.ts +0 -29
- package/build/tests/helpers/app.js +0 -104
- package/build/tests/helpers/index.d.ts +0 -7
- package/build/tests/helpers/index.js +0 -7
- package/build/tests/options.spec.d.ts +0 -7
- package/build/tests/options.spec.js +0 -126
- package/build/tests/variants.spec.d.ts +0 -7
- package/build/tests/variants.spec.js +0 -21
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jrmc/adonis-attachment
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
+
*/
|
|
7
|
+
import { persistAttachment, commit, rollback, generateVariants, preComputeUrl, } from '../utils/actions.js';
|
|
8
|
+
import { clone, getAttachmentAttributeNames, getDirtyAttachmentAttributeNames, } from '../utils/helpers.js';
|
|
9
|
+
import { defaultStateAttributeMixin } from '../utils/default_values.js';
|
|
10
|
+
// @afterFind()
|
|
11
|
+
export const afterFindHook = async (instance) => {
|
|
12
|
+
const modelInstance = instance;
|
|
13
|
+
const attachmentAttributeNames = getAttachmentAttributeNames(modelInstance);
|
|
14
|
+
await Promise.all(attachmentAttributeNames.map((attributeName) => {
|
|
15
|
+
return preComputeUrl(modelInstance, attributeName);
|
|
16
|
+
}));
|
|
17
|
+
};
|
|
18
|
+
// @afterFetch()
|
|
19
|
+
// @afterPaginate()
|
|
20
|
+
export const afterFetchHook = async (instance) => {
|
|
21
|
+
const modelInstances = instance;
|
|
22
|
+
await Promise.all(modelInstances.map((row) => afterFindHook(row)));
|
|
23
|
+
};
|
|
24
|
+
// @beforeSave()
|
|
25
|
+
export const beforeSaveHook = async (instance) => {
|
|
26
|
+
const modelInstance = instance;
|
|
27
|
+
const attachmentAttributeNames = getDirtyAttachmentAttributeNames(modelInstance);
|
|
28
|
+
/**
|
|
29
|
+
* Empty previous $attachments
|
|
30
|
+
*/
|
|
31
|
+
modelInstance.$attachments = clone(defaultStateAttributeMixin);
|
|
32
|
+
/**
|
|
33
|
+
* Set attributes Attachment type modified
|
|
34
|
+
*/
|
|
35
|
+
attachmentAttributeNames.forEach((attributeName) => modelInstance.$attachments.attributesModified.push(attributeName));
|
|
36
|
+
/**
|
|
37
|
+
* Persist attachments before saving the model to the database. This
|
|
38
|
+
* way if file saving fails we will not write anything to the
|
|
39
|
+
* database
|
|
40
|
+
*/
|
|
41
|
+
await Promise.all(attachmentAttributeNames.map((attributeName) => persistAttachment(modelInstance, attributeName)));
|
|
42
|
+
try {
|
|
43
|
+
if (modelInstance.$trx) {
|
|
44
|
+
modelInstance.$trx.after('commit', () => commit(modelInstance));
|
|
45
|
+
modelInstance.$trx.after('rollback', () => rollback(modelInstance));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await commit(modelInstance);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
await rollback(modelInstance);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// @afterSave()
|
|
57
|
+
export const afterSaveHook = async (instance) => {
|
|
58
|
+
const modelInstance = instance;
|
|
59
|
+
const attachmentAttributeNames = getAttachmentAttributeNames(modelInstance);
|
|
60
|
+
/**
|
|
61
|
+
* For all properties Attachment
|
|
62
|
+
* Launch async generation variants
|
|
63
|
+
*/
|
|
64
|
+
await Promise.all(attachmentAttributeNames.map((attributeName) => {
|
|
65
|
+
if (modelInstance.$attachments.attributesModified.includes(attributeName)) {
|
|
66
|
+
return generateVariants(modelInstance, attributeName);
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
};
|
|
70
|
+
// @beforeDelete()
|
|
71
|
+
export const beforeDeleteHook = async (instance) => {
|
|
72
|
+
const modelInstance = instance;
|
|
73
|
+
const attachmentAttributeNames = getAttachmentAttributeNames(modelInstance);
|
|
74
|
+
/**
|
|
75
|
+
* Mark all attachments for deletion
|
|
76
|
+
*/
|
|
77
|
+
attachmentAttributeNames.map((attributeName) => {
|
|
78
|
+
if (modelInstance.$attributes[attributeName]) {
|
|
79
|
+
modelInstance.$attachments.detached.push(modelInstance.$attributes[attributeName]);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* If model is using transaction, then wait for the transaction
|
|
84
|
+
* to settle
|
|
85
|
+
*/
|
|
86
|
+
if (modelInstance.$trx) {
|
|
87
|
+
modelInstance.$trx.after('commit', () => commit(modelInstance));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await commit(modelInstance);
|
|
91
|
+
}
|
|
92
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrmc/adonis-attachment",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Turn any field on your Lucid model to an attachment data type",
|
|
6
6
|
"engines": {
|
|
@@ -64,28 +64,30 @@
|
|
|
64
64
|
"./attachment_provider": "./build/providers/attachment_provider.js"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@poppinss/defer": "^1.1.
|
|
68
|
-
"exifreader": "^4.
|
|
69
|
-
"file-type": "^19.6.0"
|
|
67
|
+
"@poppinss/defer": "^1.1.1",
|
|
68
|
+
"exifreader": "^4.26.0",
|
|
69
|
+
"file-type": "^19.6.0",
|
|
70
|
+
"mime-types": "^2.1.35"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
73
|
"@adonisjs/assembler": "^7.8.2",
|
|
73
|
-
"@adonisjs/core": "^6.17.
|
|
74
|
+
"@adonisjs/core": "^6.17.1",
|
|
74
75
|
"@adonisjs/drive": "^3.2.0",
|
|
75
|
-
"@adonisjs/lucid": "^21.
|
|
76
|
+
"@adonisjs/lucid": "^21.6.0",
|
|
76
77
|
"@adonisjs/prettier-config": "^1.4.0",
|
|
77
78
|
"@adonisjs/tsconfig": "^1.4.0",
|
|
78
|
-
"@japa/assert": "^
|
|
79
|
-
"@japa/expect-type": "^2.0.
|
|
80
|
-
"@japa/file-system": "^2.3.
|
|
81
|
-
"@japa/plugin-adonisjs": "^
|
|
82
|
-
"@japa/runner": "^
|
|
83
|
-
"@poppinss/utils": "^6.
|
|
84
|
-
"@swc/core": "^1.10.
|
|
79
|
+
"@japa/assert": "^4.0.1",
|
|
80
|
+
"@japa/expect-type": "^2.0.3",
|
|
81
|
+
"@japa/file-system": "^2.3.2",
|
|
82
|
+
"@japa/plugin-adonisjs": "^4.0.0",
|
|
83
|
+
"@japa/runner": "^4.1.0",
|
|
84
|
+
"@poppinss/utils": "^6.9.2",
|
|
85
|
+
"@swc/core": "^1.10.7",
|
|
85
86
|
"@types/luxon": "^3.4.2",
|
|
86
|
-
"@types/
|
|
87
|
+
"@types/mime-types": "^2.1.4",
|
|
88
|
+
"@types/node": "^22.10.7",
|
|
87
89
|
"@types/sinon": "^17.0.3",
|
|
88
|
-
"better-sqlite3": "^11.
|
|
90
|
+
"better-sqlite3": "^11.8.0",
|
|
89
91
|
"c8": "^10.1.3",
|
|
90
92
|
"copyfiles": "^2.4.1",
|
|
91
93
|
"del-cli": "^6.0.0",
|
|
@@ -93,7 +95,7 @@
|
|
|
93
95
|
"luxon": "^3.5.0",
|
|
94
96
|
"prettier": "^3.4.2",
|
|
95
97
|
"ts-node": "^10.9.2",
|
|
96
|
-
"typescript": "^5.7.
|
|
98
|
+
"typescript": "^5.7.3",
|
|
97
99
|
"vitepress": "^1.5.0"
|
|
98
100
|
},
|
|
99
101
|
"peerDependencies": {
|
package/build/bin/test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/build/bin/test.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { assert } from '@japa/assert';
|
|
2
|
-
import { expectTypeOf } from '@japa/expect-type';
|
|
3
|
-
import { processCLIArgs, configure, run } from '@japa/runner';
|
|
4
|
-
import { createApp, initializeDatabase } from '../tests/helpers/app.js';
|
|
5
|
-
import { fileSystem } from '@japa/file-system';
|
|
6
|
-
import app from '@adonisjs/core/services/app';
|
|
7
|
-
import { BASE_URL } from '../tests/helpers/index.js';
|
|
8
|
-
let testApp;
|
|
9
|
-
processCLIArgs(process.argv.slice(2));
|
|
10
|
-
configure({
|
|
11
|
-
files: ['tests/**/*.spec.ts'],
|
|
12
|
-
plugins: [assert(), fileSystem({ basePath: BASE_URL }), expectTypeOf()],
|
|
13
|
-
setup: [
|
|
14
|
-
async () => {
|
|
15
|
-
testApp = await createApp();
|
|
16
|
-
await initializeDatabase(testApp);
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
teardown: [
|
|
20
|
-
async () => {
|
|
21
|
-
await app.terminate();
|
|
22
|
-
await testApp.terminate();
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
});
|
|
26
|
-
/*
|
|
27
|
-
|--------------------------------------------------------------------------
|
|
28
|
-
| Run tests
|
|
29
|
-
|--------------------------------------------------------------------------
|
|
30
|
-
|
|
|
31
|
-
| The following "run" method is required to execute all the tests.
|
|
32
|
-
|
|
|
33
|
-
*/
|
|
34
|
-
run();
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import https from 'node:https';
|
|
8
|
-
import { readFile } from 'node:fs/promises';
|
|
9
|
-
import { test } from '@japa/runner';
|
|
10
|
-
import app from '@adonisjs/core/services/app';
|
|
11
|
-
import drive from '@adonisjs/drive/services/main';
|
|
12
|
-
import { MultipartFileFactory } from '@adonisjs/core/factories/bodyparser';
|
|
13
|
-
import { UserFactory } from './fixtures/factories/user.js';
|
|
14
|
-
import { attachmentManager } from '../index.js';
|
|
15
|
-
test.group('attachment-manager', () => {
|
|
16
|
-
test('save method - should result in noop when attachment is created from db response', async ({ assert, }) => {
|
|
17
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
18
|
-
const attachment = attachmentManager.createFromDbResponse(JSON.stringify({
|
|
19
|
-
size: 1440,
|
|
20
|
-
name: 'foo123.jpg',
|
|
21
|
-
originalName: 'foo.jpg',
|
|
22
|
-
extname: 'jpg',
|
|
23
|
-
mimeType: 'image/jpg',
|
|
24
|
-
}));
|
|
25
|
-
assert.equal(attachment?.originalName, 'foo.jpg');
|
|
26
|
-
});
|
|
27
|
-
test('Attachment - should be null when db response is null', async ({ assert }) => {
|
|
28
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
29
|
-
const attachment = attachmentManager.createFromDbResponse(null);
|
|
30
|
-
assert.isNull(attachment);
|
|
31
|
-
});
|
|
32
|
-
test('Attachment path default is uploads', async ({ assert }) => {
|
|
33
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
34
|
-
const attachment = attachmentManager.createFromDbResponse(JSON.stringify({
|
|
35
|
-
size: 1440,
|
|
36
|
-
name: 'foo.jpg',
|
|
37
|
-
extname: 'jpg',
|
|
38
|
-
mimeType: 'image/jpg',
|
|
39
|
-
}));
|
|
40
|
-
assert.equal(attachment?.path, 'uploads/foo.jpg');
|
|
41
|
-
});
|
|
42
|
-
test('Attachment path - should be custom', async ({ assert }) => {
|
|
43
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
44
|
-
const attachment = attachmentManager.createFromDbResponse(JSON.stringify({
|
|
45
|
-
size: 1440,
|
|
46
|
-
name: 'foo.jpg',
|
|
47
|
-
extname: 'jpg',
|
|
48
|
-
mimeType: 'image/jpg',
|
|
49
|
-
}));
|
|
50
|
-
attachment?.setOptions({ folder: 'avatar' });
|
|
51
|
-
assert.equal(attachment?.path, 'avatar/foo.jpg');
|
|
52
|
-
});
|
|
53
|
-
test('Attachment get url', async ({ assert, cleanup }) => {
|
|
54
|
-
drive.fake('fs');
|
|
55
|
-
cleanup(() => drive.restore('fs'));
|
|
56
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
57
|
-
const attachment = attachmentManager.createFromDbResponse(JSON.stringify({
|
|
58
|
-
size: 1440,
|
|
59
|
-
name: 'foo.jpg',
|
|
60
|
-
extname: 'jpg',
|
|
61
|
-
mimeType: 'image/jpg',
|
|
62
|
-
}));
|
|
63
|
-
attachment?.setOptions({ folder: 'avatars' });
|
|
64
|
-
const url = await attachment?.getUrl();
|
|
65
|
-
const signedUrl = await attachment?.getSignedUrl();
|
|
66
|
-
assert.match(url, /\/drive\/fakes\/avatars\/foo\.jpg/);
|
|
67
|
-
assert.match(signedUrl, /\/drive\/fakes\/signed\/avatars\/foo\.jpg/);
|
|
68
|
-
});
|
|
69
|
-
test('Precompute file url', async ({ assert, cleanup }) => {
|
|
70
|
-
drive.fake('fs');
|
|
71
|
-
cleanup(() => drive.restore('fs'));
|
|
72
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
73
|
-
const attachment = attachmentManager.createFromDbResponse(JSON.stringify({
|
|
74
|
-
size: 1440,
|
|
75
|
-
name: 'foo.jpg',
|
|
76
|
-
extname: 'jpg',
|
|
77
|
-
mimeType: 'image/jpg',
|
|
78
|
-
}));
|
|
79
|
-
attachment?.setOptions({ preComputeUrl: true, folder: 'avatars' });
|
|
80
|
-
await attachmentManager.preComputeUrl(attachment);
|
|
81
|
-
assert.match(attachment?.url, /\/drive\/fakes\/avatars\/foo\.jpg/);
|
|
82
|
-
});
|
|
83
|
-
test('with base64 (prefix)', async ({ assert }) => {
|
|
84
|
-
const avatar = await attachmentManager.createFromBase64('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=', 'avatar.png');
|
|
85
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
86
|
-
const data = await user.serialize();
|
|
87
|
-
assert.deepEqual(data.avatar, {
|
|
88
|
-
extname: 'png',
|
|
89
|
-
meta: {
|
|
90
|
-
dimension: {
|
|
91
|
-
height: 24,
|
|
92
|
-
width: 24,
|
|
93
|
-
},
|
|
94
|
-
host: 'www.inkscape.org',
|
|
95
|
-
},
|
|
96
|
-
mimeType: 'image/png',
|
|
97
|
-
name: data.avatar.name,
|
|
98
|
-
originalName: 'avatar.png',
|
|
99
|
-
size: 965,
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
test('with base64 (no prefix)', async ({ assert }) => {
|
|
103
|
-
const avatar = await attachmentManager.createFromBase64('iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=', 'avatar.png');
|
|
104
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
105
|
-
const data = await user.serialize();
|
|
106
|
-
assert.deepEqual(data.avatar, {
|
|
107
|
-
extname: 'png',
|
|
108
|
-
meta: {
|
|
109
|
-
dimension: {
|
|
110
|
-
height: 24,
|
|
111
|
-
width: 24,
|
|
112
|
-
},
|
|
113
|
-
host: 'www.inkscape.org',
|
|
114
|
-
},
|
|
115
|
-
mimeType: 'image/png',
|
|
116
|
-
name: data.avatar.name,
|
|
117
|
-
originalName: 'avatar.png',
|
|
118
|
-
size: 965,
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
test('with buffer', async ({ assert }) => {
|
|
122
|
-
const buffer = await readFile(app.makePath('../fixtures/images/img.jpg'));
|
|
123
|
-
const avatar = await attachmentManager.createFromBuffer(buffer, 'avatar.jpg');
|
|
124
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
125
|
-
const data = await user.serialize();
|
|
126
|
-
assert.deepEqual(data.avatar, {
|
|
127
|
-
extname: 'jpg',
|
|
128
|
-
meta: {
|
|
129
|
-
dimension: {
|
|
130
|
-
height: 1313,
|
|
131
|
-
width: 1920,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
mimeType: 'image/jpeg',
|
|
135
|
-
name: data.avatar.name,
|
|
136
|
-
originalName: 'avatar.jpg',
|
|
137
|
-
size: 122851,
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
test('with file', async ({ assert }) => {
|
|
141
|
-
const file = new MultipartFileFactory()
|
|
142
|
-
.merge({
|
|
143
|
-
size: 4000000,
|
|
144
|
-
extname: 'jpg',
|
|
145
|
-
type: 'image',
|
|
146
|
-
subtype: 'jpeg',
|
|
147
|
-
})
|
|
148
|
-
.create();
|
|
149
|
-
file.tmpPath = app.makePath('../fixtures/images/img.jpg');
|
|
150
|
-
const avatar = await attachmentManager.createFromFile(file);
|
|
151
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
152
|
-
const data = await user.serialize();
|
|
153
|
-
assert.deepEqual(data.avatar, {
|
|
154
|
-
extname: 'jpg',
|
|
155
|
-
meta: {
|
|
156
|
-
dimension: {
|
|
157
|
-
height: 1313,
|
|
158
|
-
width: 1920,
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
mimeType: 'image/jpeg',
|
|
162
|
-
name: data.avatar.name,
|
|
163
|
-
originalName: 'file.jpg',
|
|
164
|
-
size: 4000000,
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
test('with path', async ({ assert }) => {
|
|
168
|
-
const path = app.makePath('../fixtures/images/img.jpg');
|
|
169
|
-
const avatar = await attachmentManager.createFromPath(path, 'file.jpg');
|
|
170
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
171
|
-
const data = await user.serialize();
|
|
172
|
-
assert.deepEqual(data.avatar, {
|
|
173
|
-
extname: 'jpg',
|
|
174
|
-
meta: {
|
|
175
|
-
dimension: {
|
|
176
|
-
height: 1313,
|
|
177
|
-
width: 1920,
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
mimeType: 'image/jpeg',
|
|
181
|
-
name: data.avatar.name,
|
|
182
|
-
originalName: 'file.jpg',
|
|
183
|
-
size: 83,
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
test('with url', async ({ assert }) => {
|
|
187
|
-
const url = new URL('https://raw.githubusercontent.com/batosai/adonis-attachment/refs/heads/develop/tests/fixtures/images/img.jpg');
|
|
188
|
-
const avatar = await attachmentManager.createFromUrl(url, 'file.jpg');
|
|
189
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
190
|
-
const data = await user.serialize();
|
|
191
|
-
assert.deepEqual(data.avatar, {
|
|
192
|
-
extname: 'jpg',
|
|
193
|
-
meta: {
|
|
194
|
-
dimension: {
|
|
195
|
-
height: 1313,
|
|
196
|
-
width: 1920,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
mimeType: 'image/jpeg',
|
|
200
|
-
name: data.avatar.name,
|
|
201
|
-
originalName: 'file.jpg',
|
|
202
|
-
size: 31,
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
test('with stream', async ({ assert }) => {
|
|
206
|
-
async function downloadImageStream(input) {
|
|
207
|
-
return await new Promise((resolve) => {
|
|
208
|
-
https.get(input, (response) => {
|
|
209
|
-
if (response.statusCode === 200) {
|
|
210
|
-
resolve(response);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
const url = new URL('https://raw.githubusercontent.com/batosai/adonis-attachment/refs/heads/develop/tests/fixtures/images/img.jpg');
|
|
216
|
-
const stream = await downloadImageStream(url);
|
|
217
|
-
const avatar = await attachmentManager.createFromStream(stream, 'file.jpg');
|
|
218
|
-
const user = await UserFactory.merge({ avatar }).create();
|
|
219
|
-
const data = await user.serialize();
|
|
220
|
-
assert.deepEqual(data.avatar, {
|
|
221
|
-
extname: 'jpg',
|
|
222
|
-
meta: {
|
|
223
|
-
dimension: {
|
|
224
|
-
height: 1313,
|
|
225
|
-
width: 1920,
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
mimeType: 'image/jpeg',
|
|
229
|
-
name: data.avatar.name,
|
|
230
|
-
originalName: 'file.jpg',
|
|
231
|
-
size: 31,
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import { test } from '@japa/runner';
|
|
8
|
-
import { UserFactory } from './fixtures/factories/user.js';
|
|
9
|
-
test.group('attachment', () => {
|
|
10
|
-
test('delete', async ({ assert }) => {
|
|
11
|
-
const user = await UserFactory.create();
|
|
12
|
-
user.avatar = null;
|
|
13
|
-
await user.save();
|
|
14
|
-
assert.isNull(user.avatar);
|
|
15
|
-
});
|
|
16
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import { test } from '@japa/runner';
|
|
8
|
-
import Configure from '@adonisjs/core/commands/configure';
|
|
9
|
-
import { IgnitorFactory } from '@adonisjs/core/factories';
|
|
10
|
-
import MakeConverter from '../commands/make/converter.js';
|
|
11
|
-
import { BASE_URL } from './helpers/index.js';
|
|
12
|
-
async function setupFakeAdonisProject(fs) {
|
|
13
|
-
await Promise.all([
|
|
14
|
-
fs.create('.env', ''),
|
|
15
|
-
fs.createJson('tsconfig.json', {}),
|
|
16
|
-
fs.create('adonisrc.ts', `export default defineConfig({})`),
|
|
17
|
-
]);
|
|
18
|
-
}
|
|
19
|
-
async function setupApp() {
|
|
20
|
-
const ignitor = new IgnitorFactory()
|
|
21
|
-
.withCoreProviders()
|
|
22
|
-
.withCoreConfig()
|
|
23
|
-
.create(BASE_URL, {
|
|
24
|
-
importer: (filePath) => {
|
|
25
|
-
if (filePath.startsWith('./') || filePath.startsWith('../')) {
|
|
26
|
-
return import(new URL(filePath, BASE_URL).href);
|
|
27
|
-
}
|
|
28
|
-
return import(filePath);
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
const app = ignitor.createApp('web');
|
|
32
|
-
await app.init();
|
|
33
|
-
await app.boot();
|
|
34
|
-
const ace = await app.container.make('ace');
|
|
35
|
-
ace.ui.switchMode('raw');
|
|
36
|
-
return { ace, app };
|
|
37
|
-
}
|
|
38
|
-
test.group('configure', (group) => {
|
|
39
|
-
group.tap((t) => t.timeout(20_000));
|
|
40
|
-
group.each.setup(async ({ context }) => setupFakeAdonisProject(context.fs));
|
|
41
|
-
test('add provider, config file, and command', async ({ assert }) => {
|
|
42
|
-
const { ace } = await setupApp();
|
|
43
|
-
const command = await ace.create(Configure, ['../../configure.js']);
|
|
44
|
-
await command.exec();
|
|
45
|
-
command.assertSucceeded();
|
|
46
|
-
await assert.fileExists('config/attachment.ts');
|
|
47
|
-
await assert.fileExists('adonisrc.ts');
|
|
48
|
-
await assert.fileContains('adonisrc.ts', '@jrmc/adonis-attachment/attachment_provider');
|
|
49
|
-
await assert.fileContains('adonisrc.ts', '@jrmc/adonis-attachment/commands');
|
|
50
|
-
});
|
|
51
|
-
test('create converter', async ({ assert }) => {
|
|
52
|
-
const { ace } = await setupApp();
|
|
53
|
-
const command = await ace.create(MakeConverter, ['thumb']);
|
|
54
|
-
await command.exec();
|
|
55
|
-
command.assertSucceeded();
|
|
56
|
-
await assert.fileExists('app/converters/thumb_converter.ts');
|
|
57
|
-
});
|
|
58
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import type { ConverterAttributes } from '../../../src/types/converter.js';
|
|
8
|
-
import type { Input } from '../../../src/types/input.js';
|
|
9
|
-
import Converter from '../../../src/converters/converter.js';
|
|
10
|
-
export default class ImageConverter extends Converter {
|
|
11
|
-
handle({ input }: ConverterAttributes): Promise<Input>;
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import Converter from '../../../src/converters/converter.js';
|
|
8
|
-
export default class ImageConverter extends Converter {
|
|
9
|
-
async handle({ input }) {
|
|
10
|
-
return input;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import User from '../models/user.js';
|
|
8
|
-
export declare const UserFactory: import("@adonisjs/lucid/types/factory").FactoryBuilderQueryContract<typeof User, import("@adonisjs/lucid/types/factory").FactoryModelContract<typeof User>>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import { readFile } from 'node:fs/promises';
|
|
8
|
-
import User from '../models/user.js';
|
|
9
|
-
import Factory from '@adonisjs/lucid/factories';
|
|
10
|
-
import app from '@adonisjs/core/services/app';
|
|
11
|
-
export const UserFactory = Factory.define(User, async ({ faker }) => {
|
|
12
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
13
|
-
const buffer = await readFile(app.makePath('../fixtures/images/img.jpg'));
|
|
14
|
-
const avatar = await attachmentManager.createFromBuffer(buffer, 'avatar.jpg');
|
|
15
|
-
return {
|
|
16
|
-
name: faker.person.lastName(),
|
|
17
|
-
avatar,
|
|
18
|
-
};
|
|
19
|
-
}).build();
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import User from '../models/user_with_variants.js';
|
|
8
|
-
export declare const UserFactory: import("@adonisjs/lucid/types/factory").FactoryBuilderQueryContract<typeof User, import("@adonisjs/lucid/types/factory").FactoryModelContract<typeof User>>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import { readFile } from 'node:fs/promises';
|
|
8
|
-
import User from '../models/user_with_variants.js';
|
|
9
|
-
import Factory from '@adonisjs/lucid/factories';
|
|
10
|
-
import app from '@adonisjs/core/services/app';
|
|
11
|
-
export const UserFactory = Factory.define(User, async ({ faker }) => {
|
|
12
|
-
const attachmentManager = await app.container.make('jrmc.attachment');
|
|
13
|
-
const buffer = await readFile(app.makePath('../fixtures/images/img.jpg'));
|
|
14
|
-
const avatar = await attachmentManager.createFromBuffer(buffer, 'avatar.jpg');
|
|
15
|
-
return {
|
|
16
|
-
name: faker.person.lastName(),
|
|
17
|
-
avatar,
|
|
18
|
-
};
|
|
19
|
-
}).build();
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import { BaseSchema } from '@adonisjs/lucid/schema';
|
|
8
|
-
export default class extends BaseSchema {
|
|
9
|
-
protected tableName: string;
|
|
10
|
-
up(): Promise<void>;
|
|
11
|
-
down(): Promise<void>;
|
|
12
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jrmc/adonis-attachment
|
|
3
|
-
*
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @copyright Jeremy Chaufourier <jeremy@chaufourier.fr>
|
|
6
|
-
*/
|
|
7
|
-
import { BaseSchema } from '@adonisjs/lucid/schema';
|
|
8
|
-
export default class extends BaseSchema {
|
|
9
|
-
tableName = 'users';
|
|
10
|
-
async up() {
|
|
11
|
-
this.schema.createTable(this.tableName, (table) => {
|
|
12
|
-
table.increments('id');
|
|
13
|
-
table.string('name');
|
|
14
|
-
table.json('avatar');
|
|
15
|
-
table.json('avatar_2');
|
|
16
|
-
table.timestamp('created_at');
|
|
17
|
-
table.timestamp('updated_at');
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
async down() {
|
|
21
|
-
this.schema.dropTable(this.tableName);
|
|
22
|
-
}
|
|
23
|
-
}
|