Skip to content

Commit c5f32e3

Browse files
committed
feat: allow to upload file directry from the url query param
1 parent 7dc94e3 commit c5f32e3

File tree

2 files changed

+125
-2
lines changed

2 files changed

+125
-2
lines changed

custom/uploader.vue

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ const progress = ref(0);
103103
const uploaded = ref(false);
104104
const uploadedSize = ref(0);
105105
106+
const downloadFileUrl = ref('');
107+
106108
watch(() => uploaded, (value) => {
107109
emit('update:emptiness', !value);
108110
});
@@ -118,9 +120,45 @@ function uploadGeneratedImage(imgBlob) {
118120
});
119121
}
120122
121-
onMounted(() => {
123+
onMounted(async () => {
122124
const previewColumnName = `previewUrl_${props.meta.pluginInstanceId}`;
123-
if (props.record[previewColumnName]) {
125+
126+
let queryValues;
127+
try {
128+
queryValues = JSON.parse(atob(route.query.values as string));
129+
} catch (e) {
130+
queryValues = {};
131+
}
132+
133+
if (queryValues[props.meta.pathColumnName]) {
134+
135+
136+
downloadFileUrl.value = queryValues[props.meta.pathColumnName];
137+
138+
const resp = await callAdminForthApi({
139+
path: `/plugin/${props.meta.pluginInstanceId}/get-file-download-url`,
140+
method: 'POST',
141+
body: {
142+
filePath: queryValues[props.meta.pathColumnName]
143+
},
144+
});
145+
if (resp.error) {
146+
adminforth.alert({
147+
message: t('Error getting file url'),
148+
variant: 'danger'
149+
});
150+
return;
151+
}
152+
const file = await downloadAsFile(resp.url);
153+
if (!file) {
154+
return;
155+
}
156+
onFileChange({
157+
target: {
158+
files: [file],
159+
},
160+
});
161+
} else if (props.record[previewColumnName]) {
124162
imgPreview.value = props.record[previewColumnName];
125163
uploaded.value = true;
126164
emit('update:emptiness', false);
@@ -300,6 +338,46 @@ const onFileChange = async (e) => {
300338
}
301339
}
302340
341+
async function downloadAsFile(url) {
342+
const options = {
343+
method: 'POST',
344+
headers: {
345+
'Content-Type': 'application/json',
346+
},
347+
body: JSON.stringify({ fileDownloadURL: url }),
348+
};
349+
const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}/adminapi/v1/plugin/${props.meta.pluginInstanceId}/proxy-download`;
350+
const resp = await fetch(fullPath, options);
351+
let isError = false;
352+
try {
353+
const jsonResp = await resp.clone().json();
354+
if (jsonResp.error) {
355+
adminforth.alert({
356+
message: t('Error uploading file'),
357+
variant: 'danger'
358+
});
359+
isError = true;
360+
}
361+
} catch (e) {
362+
363+
}
303364
365+
if (isError) {
366+
return null;
367+
}
368+
369+
const blob = await resp.blob();
370+
371+
const filename = url.split('/').pop()?.split('?')[0] || `file`;
372+
const filenameParts = filename.split('.');
373+
const extension = filenameParts.length > 1 ? filenameParts.pop() : '';
374+
const nameWithoutExt = filenameParts.join('.');
375+
const newFileName = extension
376+
? `${nameWithoutExt}_copy_${Date.now()}.${extension}`
377+
: `${filename}_copy_${Date.now()}`;
378+
379+
const file = new File([blob], newFileName, { type: blob.type });
380+
return file;
381+
}
304382
305383
</script>

index.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PluginOptions } from './types.js';
33
import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResource, Filters, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
44
import { Readable } from "stream";
55
import { RateLimiter } from "adminforth";
6+
import { url } from 'inspector/promises';
67

78
const ADMINFORTH_NOT_YET_USED_TAG = 'adminforth-candidate-for-cleanup';
89

@@ -86,6 +87,7 @@ export default class UploadPlugin extends AdminForthPlugin {
8687
minShowWidth: this.options.preview?.minShowWidth,
8788
generationPrompt: this.options.generation?.generationPrompt,
8889
recorPkFieldName: this.resourceConfig.columns.find((column: any) => column.primaryKey)?.name,
90+
pathColumnName: this.options.pathColumnName,
8991
};
9092
// define components which will be imported from other components
9193
this.componentPath('imageGenerator.vue');
@@ -411,6 +413,49 @@ export default class UploadPlugin extends AdminForthPlugin {
411413
},
412414
});
413415

416+
server.endpoint({
417+
method: 'POST',
418+
path: `/plugin/${this.pluginInstanceId}/get-file-download-url`,
419+
handler: async ({ body, adminUser }) => {
420+
const { filePath } = body;
421+
422+
const url = await this.options.storageAdapter.getDownloadUrl(filePath, 1800);
423+
424+
return {
425+
url,
426+
};
427+
},
428+
});
429+
430+
server.endpoint({
431+
method: 'POST',
432+
path: `/plugin/${this.pluginInstanceId}/proxy-download`,
433+
handler: async ({ body, response }) => {
434+
const { fileDownloadURL } = body;
435+
436+
if (!fileDownloadURL) {
437+
return { error: 'Missing fileDownloadURL' };
438+
}
439+
440+
const upstream = await fetch(fileDownloadURL);
441+
if (!upstream.ok || !upstream.body) {
442+
return { error: `Failed to download file (status ${upstream.status})` };
443+
}
444+
445+
const contentType = upstream.headers.get('content-type') || 'application/octet-stream';
446+
const contentLength = upstream.headers.get('content-length');
447+
const contentDisposition = upstream.headers.get('content-disposition');
448+
449+
response.setHeader('Content-Type', contentType);
450+
if (contentLength) response.setHeader('Content-Length', contentLength);
451+
if (contentDisposition) response.setHeader('Content-Disposition', contentDisposition);
452+
453+
//@ts-ignore Node 18+: convert Web stream to Node stream and pipe to response
454+
Readable.fromWeb(upstream.body).pipe(response.blobStream());
455+
return null;
456+
},
457+
});
458+
414459
}
415460

416461
}

0 commit comments

Comments
 (0)