Merge branch 'development' into cursor-testing

This commit is contained in:
shb 2025-06-17 15:11:28 +08:00
commit 9333c7085a
8 changed files with 2983 additions and 31 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ node_modules
# Uploads directory
public/uploads/
server/api/dms/sample-files-for-s3

View File

@ -328,8 +328,23 @@ const performUpload = async () => {
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log(file);
// Here you would implement actual file upload
console.log('Uploading:', file.name, 'with metadata:', metadata);
const formData = new FormData();
formData.append('fileName', metadata.name);
formData.append('file', file);
console.log(formData);
const response = await fetch('/api/dms/upload-file', {
method: "POST",
// Let browser automatically set headers for multipart/form-data
body: formData,
});
console.log(response);
// console.log('Uploading:', file.name, 'with metadata:', metadata);
}
success(`Successfully uploaded ${selectedFiles.value.length} file(s)`);

1567
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,10 @@
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.8.0",
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
"@types/archiver": "^6.0.2",
"@types/jsonwebtoken": "^9.0.9",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@vite-pwa/nuxt": "^0.1.0",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.16.1",
@ -19,12 +22,11 @@
"nuxt-icon": "^0.1.7",
"nuxt-security": "^0.13.0",
"nuxt-typed-router": "^3.2.5",
"postcss-import": "^15.1.0",
"@types/multer": "^1.4.11",
"@types/mime-types": "^2.1.4",
"@types/archiver": "^6.0.2"
"postcss-import": "^15.1.0"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.828.0",
"@aws-sdk/s3-request-presigner": "^3.830.0",
"@babel/eslint-parser": "^7.19.1",
"@codemirror/lang-html": "^6.4.3",
"@codemirror/lang-javascript": "^6.1.6",
@ -44,6 +46,8 @@
"@fullcalendar/scrollgrid": "^5.11.3",
"@fullcalendar/timegrid": "^5.11.3",
"@fullcalendar/vue3": "^5.11.2",
"@iconify/json": "^2.2.156",
"@iconify/vue": "^4.1.1",
"@kiwicom/eslint-config": "^12.7.3",
"@pinia/nuxt": "^0.4.11",
"@popperjs/core": "^2.11.8",
@ -54,19 +58,34 @@
"@vueuse/core": "^9.5.0",
"@vueuse/nuxt": "^9.5.0",
"apexcharts": "^3.36.0",
"archiver": "^6.0.1",
"chart.js": "^3.9.1",
"codemirror": "^6.0.1",
"cross-env": "^7.0.3",
"crypto-js": "^4.1.1",
"date-fns": "^2.30.0",
"file-type": "^18.7.0",
"floating-vue": "^2.0.0-beta.24",
"fuse.js": "^7.0.0",
"joi": "^17.11.0",
"jose": "^5.1.3",
"jsonwebtoken": "^8.5.1",
"jspdf": "^2.5.1",
"ldapjs": "^3.0.7",
"luxon": "^3.1.0",
"maska": "^1.5.0",
"mime-types": "^2.1.35",
"multer": "^1.4.5-lts.1",
"node-stream-zip": "^1.15.0",
"passport": "^0.7.0",
"passport-ldapauth": "^3.0.1",
"passport-oauth2": "^1.7.0",
"passport-saml": "^3.2.4",
"pinia": "^2.1.6",
"prettier": "^2.8.1",
"prisma": "^5.1.1",
"sass": "^1.62.0",
"sharp": "^0.32.6",
"swiper": "^8.4.4",
"thememirror": "^2.0.1",
"uuid": "^10.0.0",
@ -84,24 +103,7 @@
"vue3-click-away": "^1.2.4",
"vue3-dropzone": "^2.0.1",
"vue3-recaptcha-v2": "^2.0.2",
"vuedraggable": "^4.1.0",
"multer": "^1.4.5-lts.1",
"file-type": "^18.7.0",
"mime-types": "^2.1.35",
"sharp": "^0.32.6",
"ldapjs": "^3.0.7",
"passport": "^0.7.0",
"passport-ldapauth": "^3.0.1",
"passport-oauth2": "^1.7.0",
"passport-saml": "^3.2.4",
"jose": "^5.1.3",
"node-stream-zip": "^1.15.0",
"archiver": "^6.0.1",
"@iconify/vue": "^4.1.1",
"@iconify/json": "^2.2.156",
"date-fns": "^2.30.0",
"fuse.js": "^7.0.0",
"joi": "^17.11.0"
"vuedraggable": "^4.1.0"
},
"overrides": {
"luxon": "^3.1.0"

126
pages/test.vue Normal file
View File

@ -0,0 +1,126 @@
<script setup>
import { ref } from 'vue';
definePageMeta({
title: "API Test Page"
});
const message = ref('');
const formData = ref({
name: '',
animal: ''
});
const selectedFile = ref(null);
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
// Check file size (10MB limit)
if (file.size > 10 * 1024 * 1024) {
alert('File size must be less than 10MB');
event.target.value = ''; // Clear the input
selectedFile.value = null;
return;
}
selectedFile.value = file;
}
}
async function handleSubmit() {
try {
const multipartFormData = new FormData();
multipartFormData.append('name', formData.value.name);
multipartFormData.append('animal', formData.value.animal);
// Only send file metadata if a file is selected
if (selectedFile.value) {
multipartFormData.append('fileName', selectedFile.value.name);
multipartFormData.append('fileType', selectedFile.value.type);
}
const response = await fetch('/api/test/test-response', {
method: 'POST',
body: multipartFormData
});
const data = await response.json();
message.value = data.message;
// If we have a file and received a signed URL, upload to S3
if (selectedFile.value && data.signedUrl) {
try {
const s3Response = await fetch(data.signedUrl, {
method: 'PUT',
body: selectedFile.value,
headers: {
'Content-Type': selectedFile.value.type
}
});
if (!s3Response.ok) {
throw new Error('Failed to upload file to S3');
}
message.value += '\nFile successfully uploaded to S3!';
} catch (s3Error) {
message.value += '\nError uploading file to S3: ' + s3Error.message;
}
}
} catch (error) {
message.value = 'Error: ' + error.message;
}
}
</script>
<template>
<div class="flex flex-col items-center justify-center min-h-screen p-4 space-y-6">
<!-- Form Section -->
<div class="w-full max-w-md space-y-4">
<form @submit.prevent="handleSubmit" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Your Name</label>
<input
id="name"
v-model="formData.name"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter your name"
required
/>
</div>
<div>
<label for="animal" class="block text-sm font-medium text-gray-700 mb-1">Favorite Animal</label>
<input
id="animal"
v-model="formData.animal"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter your favorite animal"
required
/>
</div>
<div>
<label for="file" class="block text-sm font-medium text-gray-700 mb-1">Upload File (Max 10MB)</label>
<input
id="file"
type="file"
@change="handleFileSelect"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
</div>
<button
type="submit"
class="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
>
Submit
</button>
</form>
</div>
<!-- Response Message -->
<pre v-if="message" class="mt-4 p-4 bg-gray-100 rounded w-full max-w-md overflow-x-auto">{{ message }}</pre>
</div>
</template>

View File

@ -0,0 +1,83 @@
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; // ES Modules import
import { readMultipartFormData } from 'h3';
export default defineEventHandler(async (event) => {
const parts = [];
// Allow headers for specific origins
// setHeader(event, 'Access-Control-Allow-Origin', 'http://localhost:3000');
// setHeader(event, 'Access-Control-Allow-Methods', 'POST, OPTIONS');
// setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type');
if (event.method === 'OPTIONS') {
return new Response(null, { status: 204 });
}
try {
const parts = await readMultipartFormData(event, { maxSize: 20 * 1024 * 1024});
if (!parts) {
return {
status: 400,
message: "Bad request. No parts found."
}
}
} catch (error) {
console.error('Failed to read multi-part data:', error);
return {
status: 500,
message: "Failed to read multi-part data"
}
}
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
}
}
})

View File

@ -0,0 +1,67 @@
import { readMultipartFormData } from 'h3';
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
export default defineEventHandler(async (event) => {
const s3 = new S3Client({
region: 'ap-southeast-1',
credentials: {
accessKeyId: process.env.NUXT_AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.NUXT_AWS_SECRET_ACCESS_KEY,
}
});
try {
const parts = await readMultipartFormData(event, { maxSize: 1024 * 1024 }); // 1MB limit since we're only handling metadata
if (!parts) {
return {
status: 400,
message: "No form data received"
};
}
// Extract form data
const name = parts.find(p => p.name === 'name')?.data.toString() || '';
const animal = parts.find(p => p.name === 'animal')?.data.toString() || '';
const fileName = parts.find(p => p.name === 'fileName')?.data.toString();
const fileType = parts.find(p => p.name === 'fileType')?.data.toString();
// Log the received data
console.log('Received name:', name);
console.log('Received favorite animal:', animal);
// Prepare response message
let message = `You are ${name} and your favorite animal is ${animal}`;
let signedUrl = null;
if (fileName && fileType) {
console.log('Received file metadata:', { fileName, fileType });
message += `. You will upload the file: ${fileName}`;
// Generate a unique key using timestamp and filename
const uploadKey = `uploads/${Date.now()}-${fileName}`;
const s3Command = new PutObjectCommand({
Bucket: process.env.NUXT_AWS_BUCKET,
Key: uploadKey,
ContentType: fileType,
});
signedUrl = await getSignedUrl(s3, s3Command, { expiresIn: 60 });
}
return {
status: 200,
message,
signedUrl
};
} catch (error) {
console.error("Error processing request:", error);
return {
status: 500,
message: "Error processing request",
error: error.message
};
}
});

1107
yarn.lock

File diff suppressed because it is too large Load Diff