@adminforth/upload 1.0.29 → 1.0.31-next.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/ChangeLog.md CHANGED
@@ -1,4 +1,9 @@
1
1
 
2
+ ## [1.0.30] - 2024-12-26
3
+
4
+ ### Improved
5
+
6
+ - Add 'record' param for s3Path callback in edit mode
2
7
 
3
8
  ## [1.0.24] - 2023-12-24
4
9
 
@@ -70,7 +70,9 @@ import { computed, ref, onMounted, watch } from 'vue'
70
70
  import { callAdminForthApi } from '@/utils'
71
71
  import { IconMagic } from '@iconify-prerendered/vue-mdi';
72
72
  import { useI18n } from 'vue-i18n';
73
+ import { useRoute } from 'vue-router';
73
74
 
75
+ const route = useRoute();
74
76
  const { t } = useI18n();
75
77
 
76
78
  const inputId = computed(() => `dropzone-file-${props.meta.pluginInstanceId}`);
@@ -233,6 +235,7 @@ const onFileChange = async (e) => {
233
235
  contentType: type,
234
236
  size,
235
237
  originalExtension: extension,
238
+ recordPk: route?.params?.primaryKey,
236
239
  },
237
240
  });
238
241
 
@@ -70,7 +70,9 @@ import { computed, ref, onMounted, watch } from 'vue'
70
70
  import { callAdminForthApi } from '@/utils'
71
71
  import { IconMagic } from '@iconify-prerendered/vue-mdi';
72
72
  import { useI18n } from 'vue-i18n';
73
+ import { useRoute } from 'vue-router';
73
74
 
75
+ const route = useRoute();
74
76
  const { t } = useI18n();
75
77
 
76
78
  const inputId = computed(() => `dropzone-file-${props.meta.pluginInstanceId}`);
@@ -233,6 +235,7 @@ const onFileChange = async (e) => {
233
235
  contentType: type,
234
236
  size,
235
237
  originalExtension: extension,
238
+ recordPk: route?.params?.primaryKey,
236
239
  },
237
240
  });
238
241
 
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
11
11
  import { ExpirationStatus, GetObjectCommand, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
12
- import { AdminForthPlugin, suggestIfTypo } from "adminforth";
12
+ import { AdminForthPlugin, Filters, suggestIfTypo } from "adminforth";
13
13
  import { Readable } from "stream";
14
14
  import { RateLimiter } from "adminforth";
15
15
  const ADMINFORTH_NOT_YET_USED_TAG = 'adminforth-candidate-for-cleanup';
@@ -25,13 +25,20 @@ export default class UploadPlugin extends AdminForthPlugin {
25
25
  return __awaiter(this, void 0, void 0, function* () {
26
26
  // check that lifecyle rule "adminforth-unused-cleaner" exists
27
27
  const CLEANUP_RULE_ID = 'adminforth-unused-cleaner';
28
- const s3 = new S3({
29
- credentials: {
30
- accessKeyId: this.options.s3AccessKeyId,
31
- secretAccessKey: this.options.s3SecretAccessKey,
32
- },
33
- region: this.options.s3Region,
34
- });
28
+ let s3;
29
+ try {
30
+ s3 = new S3({
31
+ credentials: {
32
+ accessKeyId: this.options.s3AccessKeyId,
33
+ secretAccessKey: this.options.s3SecretAccessKey,
34
+ },
35
+ region: this.options.s3Region,
36
+ });
37
+ }
38
+ catch (e) {
39
+ console.error('Unable to connect to S3. Upload will not work. Skipping setup of lifecycle rule', e);
40
+ return;
41
+ }
35
42
  // check bucket exists
36
43
  const bucketExists = s3.headBucket({ Bucket: this.options.s3Bucket });
37
44
  if (!bucketExists) {
@@ -105,7 +112,7 @@ export default class UploadPlugin extends AdminForthPlugin {
105
112
  const { pathColumnName } = this.options;
106
113
  const pathColumnIndex = resourceConfig.columns.findIndex((column) => column.name === pathColumnName);
107
114
  if (pathColumnIndex === -1) {
108
- throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.name}"`);
115
+ throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.label}"`);
109
116
  }
110
117
  if ((_a = this.options.generation) === null || _a === void 0 ? void 0 : _a.fieldsForContext) {
111
118
  (_b = this.options.generation) === null || _b === void 0 ? void 0 : _b.fieldsForContext.forEach((field) => {
@@ -283,8 +290,8 @@ export default class UploadPlugin extends AdminForthPlugin {
283
290
  return { ok: true };
284
291
  }));
285
292
  // add edit postSave hook to delete old file and remove tag from new file
286
- resourceConfig.hooks.edit.afterSave.push((_s) => __awaiter(this, [_s], void 0, function* ({ record, oldRecord }) {
287
- if (record[virtualColumn.name] || record[virtualColumn.name] === null) {
293
+ resourceConfig.hooks.edit.afterSave.push((_s) => __awaiter(this, [_s], void 0, function* ({ updates, oldRecord }) {
294
+ if (updates[virtualColumn.name] || updates[virtualColumn.name] === null) {
288
295
  const s3 = new S3({
289
296
  credentials: {
290
297
  accessKeyId: this.options.s3AccessKeyId,
@@ -313,12 +320,12 @@ export default class UploadPlugin extends AdminForthPlugin {
313
320
  console.error(`Error setting tag ${ADMINFORTH_NOT_YET_USED_TAG} to true for object ${oldRecord[pathColumnName]}. File will not be auto-cleaned up`, e);
314
321
  }
315
322
  }
316
- if (record[virtualColumn.name] !== null) {
323
+ if (updates[virtualColumn.name] !== null) {
317
324
  // remove tag from new file
318
325
  // in this case we let it crash if it fails: this is a new file which just was uploaded.
319
326
  yield s3.putObjectTagging({
320
327
  Bucket: this.options.s3Bucket,
321
- Key: record[pathColumnName],
328
+ Key: updates[pathColumnName],
322
329
  Tagging: {
323
330
  TagSet: []
324
331
  }
@@ -339,14 +346,20 @@ export default class UploadPlugin extends AdminForthPlugin {
339
346
  method: 'POST',
340
347
  path: `/plugin/${this.pluginInstanceId}/get_s3_upload_url`,
341
348
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
342
- var _b;
343
- const { originalFilename, contentType, size, originalExtension } = body;
349
+ var _b, _c;
350
+ const { originalFilename, contentType, size, originalExtension, recordPk } = body;
344
351
  if (this.options.allowedFileExtensions && !this.options.allowedFileExtensions.includes(originalExtension)) {
345
352
  return {
346
353
  error: `File extension "${originalExtension}" is not allowed, allowed extensions are: ${this.options.allowedFileExtensions.join(', ')}`
347
354
  };
348
355
  }
349
- const s3Path = this.options.s3Path({ originalFilename, originalExtension, contentType });
356
+ let record = undefined;
357
+ if (recordPk) {
358
+ // get record by recordPk
359
+ const pkName = (_b = this.resourceConfig.columns.find((column) => column.primaryKey)) === null || _b === void 0 ? void 0 : _b.name;
360
+ record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(pkName, recordPk)]);
361
+ }
362
+ const s3Path = this.options.s3Path({ originalFilename, originalExtension, contentType, record });
350
363
  if (s3Path.startsWith('/')) {
351
364
  throw new Error('s3Path should not start with /, please adjust s3path function to not return / at the start of the path');
352
365
  }
@@ -370,7 +383,7 @@ export default class UploadPlugin extends AdminForthPlugin {
370
383
  unhoistableHeaders: new Set(['x-amz-tagging']),
371
384
  });
372
385
  let previewUrl;
373
- if ((_b = this.options.preview) === null || _b === void 0 ? void 0 : _b.previewUrl) {
386
+ if ((_c = this.options.preview) === null || _c === void 0 ? void 0 : _c.previewUrl) {
374
387
  previewUrl = this.options.preview.previewUrl({ s3Path });
375
388
  }
376
389
  else if (this.options.s3ACL === 'public-read') {
@@ -411,15 +424,15 @@ export default class UploadPlugin extends AdminForthPlugin {
411
424
  server.endpoint({
412
425
  method: 'POST',
413
426
  path: `/plugin/${this.pluginInstanceId}/generate_images`,
414
- handler: (_c) => __awaiter(this, [_c], void 0, function* ({ body, headers }) {
415
- var _d, _e;
427
+ handler: (_d) => __awaiter(this, [_d], void 0, function* ({ body, headers }) {
428
+ var _e, _f;
416
429
  const { prompt } = body;
417
430
  if (this.options.generation.provider !== 'openai-dall-e') {
418
431
  throw new Error(`Provider ${this.options.generation.provider} is not supported`);
419
432
  }
420
- if ((_d = this.options.generation.rateLimit) === null || _d === void 0 ? void 0 : _d.limit) {
433
+ if ((_e = this.options.generation.rateLimit) === null || _e === void 0 ? void 0 : _e.limit) {
421
434
  // rate limit
422
- const { error } = RateLimiter.checkRateLimit(this.pluginInstanceId, (_e = this.options.generation.rateLimit) === null || _e === void 0 ? void 0 : _e.limit, this.adminforth.auth.getClientIp(headers));
435
+ const { error } = RateLimiter.checkRateLimit(this.pluginInstanceId, (_f = this.options.generation.rateLimit) === null || _f === void 0 ? void 0 : _f.limit, this.adminforth.auth.getClientIp(headers));
423
436
  if (error) {
424
437
  return { error: this.options.generation.rateLimit.errorMessage };
425
438
  }
@@ -455,7 +468,7 @@ export default class UploadPlugin extends AdminForthPlugin {
455
468
  server.endpoint({
456
469
  method: 'GET',
457
470
  path: `/plugin/${this.pluginInstanceId}/cors-proxy`,
458
- handler: (_f) => __awaiter(this, [_f], void 0, function* ({ query, response }) {
471
+ handler: (_g) => __awaiter(this, [_g], void 0, function* ({ query, response }) {
459
472
  const { url } = query;
460
473
  const resp = yield fetch(url);
461
474
  response.setHeader('Content-Type', resp.headers.get('Content-Type'));
package/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import { PluginOptions } from './types.js';
3
3
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
4
4
  import { ExpirationStatus, GetObjectCommand, ObjectCannedACL, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
5
- import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResourcePages, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
5
+ import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResource, Filters, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
6
6
  import { Readable } from "stream";
7
7
  import { RateLimiter } from "adminforth";
8
8
 
@@ -25,14 +25,20 @@ export default class UploadPlugin extends AdminForthPlugin {
25
25
  async setupLifecycleRule() {
26
26
  // check that lifecyle rule "adminforth-unused-cleaner" exists
27
27
  const CLEANUP_RULE_ID = 'adminforth-unused-cleaner';
28
- const s3 = new S3({
29
- credentials: {
30
- accessKeyId: this.options.s3AccessKeyId,
31
- secretAccessKey: this.options.s3SecretAccessKey,
32
- },
33
28
 
34
- region: this.options.s3Region,
35
- });
29
+ let s3;
30
+ try {
31
+ s3 = new S3({
32
+ credentials: {
33
+ accessKeyId: this.options.s3AccessKeyId,
34
+ secretAccessKey: this.options.s3SecretAccessKey,
35
+ },
36
+ region: this.options.s3Region,
37
+ });
38
+ } catch (e) {
39
+ console.error('Unable to connect to S3. Upload will not work. Skipping setup of lifecycle rule', e);
40
+ return;
41
+ }
36
42
  // check bucket exists
37
43
  const bucketExists = s3.headBucket({ Bucket: this.options.s3Bucket })
38
44
  if (!bucketExists) {
@@ -94,7 +100,7 @@ export default class UploadPlugin extends AdminForthPlugin {
94
100
  record[`previewUrl_${this.pluginInstanceId}`] = previewUrl;
95
101
  }
96
102
 
97
- async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: any) {
103
+ async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
98
104
  super.modifyResourceConfig(adminforth, resourceConfig);
99
105
  // after column to store the path of the uploaded file, add new VirtualColumn,
100
106
  // show only in edit and create views
@@ -102,7 +108,7 @@ export default class UploadPlugin extends AdminForthPlugin {
102
108
  const { pathColumnName } = this.options;
103
109
  const pathColumnIndex = resourceConfig.columns.findIndex((column: any) => column.name === pathColumnName);
104
110
  if (pathColumnIndex === -1) {
105
- throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.name}"`);
111
+ throw new Error(`Column with name "${pathColumnName}" not found in resource "${resourceConfig.label}"`);
106
112
  }
107
113
 
108
114
  if (this.options.generation?.fieldsForContext) {
@@ -315,9 +321,9 @@ export default class UploadPlugin extends AdminForthPlugin {
315
321
 
316
322
 
317
323
  // add edit postSave hook to delete old file and remove tag from new file
318
- resourceConfig.hooks.edit.afterSave.push(async ({ record, oldRecord }: { record: any, oldRecord: any }) => {
324
+ resourceConfig.hooks.edit.afterSave.push(async ({ updates, oldRecord }: { updates: any, oldRecord: any }) => {
319
325
 
320
- if (record[virtualColumn.name] || record[virtualColumn.name] === null) {
326
+ if (updates[virtualColumn.name] || updates[virtualColumn.name] === null) {
321
327
  const s3 = new S3({
322
328
  credentials: {
323
329
  accessKeyId: this.options.s3AccessKeyId,
@@ -347,12 +353,12 @@ export default class UploadPlugin extends AdminForthPlugin {
347
353
  console.error(`Error setting tag ${ADMINFORTH_NOT_YET_USED_TAG} to true for object ${oldRecord[pathColumnName]}. File will not be auto-cleaned up`, e);
348
354
  }
349
355
  }
350
- if (record[virtualColumn.name] !== null) {
356
+ if (updates[virtualColumn.name] !== null) {
351
357
  // remove tag from new file
352
358
  // in this case we let it crash if it fails: this is a new file which just was uploaded.
353
359
  await s3.putObjectTagging({
354
360
  Bucket: this.options.s3Bucket,
355
- Key: record[pathColumnName],
361
+ Key: updates[pathColumnName],
356
362
  Tagging: {
357
363
  TagSet: []
358
364
  }
@@ -376,7 +382,7 @@ export default class UploadPlugin extends AdminForthPlugin {
376
382
  method: 'POST',
377
383
  path: `/plugin/${this.pluginInstanceId}/get_s3_upload_url`,
378
384
  handler: async ({ body }) => {
379
- const { originalFilename, contentType, size, originalExtension } = body;
385
+ const { originalFilename, contentType, size, originalExtension, recordPk } = body;
380
386
 
381
387
  if (this.options.allowedFileExtensions && !this.options.allowedFileExtensions.includes(originalExtension)) {
382
388
  return {
@@ -384,7 +390,16 @@ export default class UploadPlugin extends AdminForthPlugin {
384
390
  };
385
391
  }
386
392
 
387
- const s3Path: string = this.options.s3Path({ originalFilename, originalExtension, contentType });
393
+ let record = undefined;
394
+ if (recordPk) {
395
+ // get record by recordPk
396
+ const pkName = this.resourceConfig.columns.find((column: any) => column.primaryKey)?.name;
397
+ record = await this.adminforth.resource(this.resourceConfig.resourceId).get(
398
+ [Filters.EQ(pkName, recordPk)]
399
+ )
400
+ }
401
+
402
+ const s3Path: string = this.options.s3Path({ originalFilename, originalExtension, contentType, record });
388
403
  if (s3Path.startsWith('/')) {
389
404
  throw new Error('s3Path should not start with /, please adjust s3path function to not return / at the start of the path');
390
405
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@adminforth/upload",
3
- "version": "1.0.29",
3
+ "version": "1.0.31-next.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "rollout": "tsc && rsync -av --exclude 'node_modules' custom dist/ && npm version patch && npm publish --access public",
9
9
  "prepare": "npm link adminforth",
10
+ "rollout-next": "npm run build && npm version prerelease --preid=next && npm publish --tag next",
10
11
  "build": "tsc"
11
12
  },
12
13
  "type": "module",
package/types.ts CHANGED
@@ -50,11 +50,16 @@ export type PluginOptions = {
50
50
  * example:
51
51
  *
52
52
  * ```typescript
53
- * s3Path: ({record, originalFilename}) => `/aparts/${record.id}/${originalFilename}`
53
+ * s3Path: ({originalFilename}) => `/aparts/${originalFilename}`
54
54
  * ```
55
55
  *
56
56
  */
57
- s3Path: ({originalFilename, originalExtension, contentType}) => string,
57
+ s3Path: ({originalFilename, originalExtension, contentType, record }: {
58
+ originalFilename: string,
59
+ originalExtension: string,
60
+ contentType: string,
61
+ record?: any,
62
+ }) => string,
58
63
 
59
64
 
60
65
  preview: {