Merged AWS upload functionality

Uploading now works with AWS. Documentation can be found in dms-api.md at the root folder.
This commit is contained in:
shb 2025-06-18 12:00:36 +08:00
parent 9333c7085a
commit 384d571997
3 changed files with 114 additions and 83 deletions

View File

@ -322,29 +322,37 @@ const performUpload = async () => {
const file = selectedFiles.value[i]; const file = selectedFiles.value[i];
const metadata = fileMetadata.value[i]; const metadata = fileMetadata.value[i];
// Simulate upload progress // First get the signed URL
for (let progress = 0; progress <= 100; progress += 10) {
uploadProgress.value = Math.round(((i * 100) + progress) / selectedFiles.value.length);
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log(file);
// Here you would implement actual file upload
const formData = new FormData(); const formData = new FormData();
formData.append('fileName', metadata.name); formData.append('fileName', metadata.name);
formData.append('file', file); formData.append('fileType', file.type);
console.log(formData); const signedUrlResponse = await fetch('/api/dms/upload-file', {
const response = await fetch('/api/dms/upload-file', {
method: "POST", method: "POST",
// Let browser automatically set headers for multipart/form-data
body: formData, body: formData,
}); });
console.log(response);
// console.log('Uploading:', file.name, 'with metadata:', metadata); const signedUrlData = await signedUrlResponse.json();
if (!signedUrlResponse.ok || !signedUrlData.signedUrl) {
throw new Error(signedUrlData.message || 'Failed to get signed URL');
}
// Upload directly to S3 using the signed URL
const uploadResponse = await fetch(signedUrlData.signedUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type
}
});
if (!uploadResponse.ok) {
throw new Error(`Failed to upload file ${metadata.name} to S3`);
}
// Update progress
uploadProgress.value = Math.round(((i + 1) * 100) / selectedFiles.value.length);
} }
success(`Successfully uploaded ${selectedFiles.value.length} file(s)`); success(`Successfully uploaded ${selectedFiles.value.length} file(s)`);

View File

@ -115,4 +115,45 @@
"message": "DMS settings updated successfully", "message": "DMS settings updated successfully",
"data": { "settingID": 1 } "data": { "settingID": 1 }
} }
``` ```
## Files
### POST /api/dms/upload-file
- **Description**: Generates a signed URL for direct file upload to S3
- **Method**: POST
- **Content-Type**: multipart/form-data
- **Request Body**:
```json
{
"fileName": "example.pdf",
"fileType": "application/pdf"
}
```
- **Required Fields**: fileName, fileType
- **Response Example**:
```json
{
"status": 200,
"message": "Signed URL generated for file: example.pdf",
"signedUrl": "https://bucket-name.s3.region.amazonaws.com/..."
}
```
- **Error Responses**:
```json
{
"status": 400,
"message": "Missing required fields { fileName, fileType }"
}
```
```json
{
"status": 500,
"message": "Failed to generate signed URL",
"error": "Error details..."
}
```
- **Notes**:
- The signed URL expires after 60 seconds
- Use the returned signedUrl with a PUT request to upload the file directly to S3
- Include the file's Content-Type header when uploading to S3

View File

@ -1,21 +1,19 @@
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; // ES Modules import import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { readMultipartFormData } from 'h3'; import { readMultipartFormData } from 'h3';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
// Create S3 client with config
const parts = []; const client = new S3Client({
region: process.env.NUXT_AWS_REGION,
// Allow headers for specific origins credentials: {
// setHeader(event, 'Access-Control-Allow-Origin', 'http://localhost:3000'); accessKeyId: process.env.NUXT_AWS_ACCESS_KEY_ID,
// setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS'); secretAccessKey: process.env.NUXT_AWS_SECRET_ACCESS_KEY
// setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type'); }
});
if (event.method === 'OPTIONS') {
return new Response(null, { status: 204 });
}
try { try {
const parts = await readMultipartFormData(event, { maxSize: 20 * 1024 * 1024}); const parts = await readMultipartFormData(event, { maxSize: 1024 * 1024 }); // 1MB limit since we're only handling metadata
if (!parts) { if (!parts) {
return { return {
@ -23,61 +21,45 @@ export default defineEventHandler(async (event) => {
message: "Bad request. No parts found." message: "Bad request. No parts found."
} }
} }
// Extract form data
const fileNamePart = parts.find(p => p.name === "fileName");
const fileTypePart = parts.find(p => p.name === "fileType");
if (!fileNamePart || !fileTypePart) {
return {
status: 400,
message: "Missing required fields { fileName, fileType }"
}
}
const fileName = fileNamePart.data.toString();
const fileType = fileTypePart.data.toString();
// Generate a unique key using timestamp and filename
const uploadKey = `uploads/${Date.now()}-${fileName}`;
const command = new PutObjectCommand({
Bucket: process.env.NUXT_AWS_BUCKET,
Key: uploadKey,
ContentType: fileType,
});
// Generate signed URL
const signedUrl = await getSignedUrl(client, command, { expiresIn: 60 });
return {
status: 200,
message: `Signed URL generated for file: ${fileName}`,
signedUrl
};
} catch (error) { } catch (error) {
console.error('Failed to read multi-part data:', error); console.error('Error processing request:', error);
return { return {
status: 500, status: 500,
message: "Failed to read multi-part data" message: "Failed to generate signed URL",
} error: error.message
}
console.log("Hello from the backend!");
const S3_Config = {
region: process.env.NUXT_AWS_REGION,
credentials: {
accessKeyId: process.env.NUXT_AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.NUXT_AWS_SECRET_ACCESS_KEY
}
}
// Create S3 client with config
const client = new S3Client(S3_Config);
// const { fileName, file } = await readMultipartFormData(event);
const fileNamePart = parts.find(p => p.name === "fileName");
const filePart = parts.find(p => p.name === "file");
if (!fileNamePart || !filePart) {
return {
status: 400,
message: "Missing required fields { fileName, file }"
}
}
const fileName = fileNamePart.data.toString();
const file = filePart.data;
const input = {
Bucket: process.env.NUXT_AWS_BUCKET,
Key: fileName,
Body: file,
ContentType: filePart.type
}
const command = new PutObjectCommand(input);
try {
const response = await client.send(command);
console.log(response);
return response;
} catch (error) {
console.error(error);
return {
status: 500,
message: "Failed to upload file to S3",
error: error
} }
} }
}) })