generated from corrad-software/corrad-af-2024
Compare commits
5 Commits
6cb4396f20
...
384d571997
Author | SHA1 | Date | |
---|---|---|---|
![]() |
384d571997 | ||
![]() |
9333c7085a | ||
![]() |
0ba58a1efb | ||
![]() |
ffec2a43ee | ||
![]() |
40cf8ebab5 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,3 +23,4 @@ node_modules
|
|||||||
|
|
||||||
# Uploads directory
|
# Uploads directory
|
||||||
public/uploads/
|
public/uploads/
|
||||||
|
server/api/dms/sample-files-for-s3
|
||||||
|
@ -322,14 +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) {
|
const formData = new FormData();
|
||||||
uploadProgress.value = Math.round(((i * 100) + progress) / selectedFiles.value.length);
|
formData.append('fileName', metadata.name);
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
formData.append('fileType', file.type);
|
||||||
|
|
||||||
|
const signedUrlResponse = await fetch('/api/dms/upload-file', {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedUrlData = await signedUrlResponse.json();
|
||||||
|
|
||||||
|
if (!signedUrlResponse.ok || !signedUrlData.signedUrl) {
|
||||||
|
throw new Error(signedUrlData.message || 'Failed to get signed URL');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here you would implement actual file upload
|
// Upload directly to S3 using the signed URL
|
||||||
console.log('Uploading:', file.name, 'with metadata:', metadata);
|
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)`);
|
||||||
|
43
dms-api.md
43
dms-api.md
@ -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
|
1567
package-lock.json
generated
1567
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -11,7 +11,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.8.0",
|
"@nuxtjs/tailwindcss": "^6.8.0",
|
||||||
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
|
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
|
||||||
|
"@types/archiver": "^6.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"@types/multer": "^1.4.11",
|
||||||
"@vite-pwa/nuxt": "^0.1.0",
|
"@vite-pwa/nuxt": "^0.1.0",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-plugin-vue": "^9.16.1",
|
"eslint-plugin-vue": "^9.16.1",
|
||||||
@ -19,12 +22,11 @@
|
|||||||
"nuxt-icon": "^0.1.7",
|
"nuxt-icon": "^0.1.7",
|
||||||
"nuxt-security": "^0.13.0",
|
"nuxt-security": "^0.13.0",
|
||||||
"nuxt-typed-router": "^3.2.5",
|
"nuxt-typed-router": "^3.2.5",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0"
|
||||||
"@types/multer": "^1.4.11",
|
|
||||||
"@types/mime-types": "^2.1.4",
|
|
||||||
"@types/archiver": "^6.0.2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.828.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.830.0",
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
"@babel/eslint-parser": "^7.19.1",
|
||||||
"@codemirror/lang-html": "^6.4.3",
|
"@codemirror/lang-html": "^6.4.3",
|
||||||
"@codemirror/lang-javascript": "^6.1.6",
|
"@codemirror/lang-javascript": "^6.1.6",
|
||||||
@ -44,6 +46,8 @@
|
|||||||
"@fullcalendar/scrollgrid": "^5.11.3",
|
"@fullcalendar/scrollgrid": "^5.11.3",
|
||||||
"@fullcalendar/timegrid": "^5.11.3",
|
"@fullcalendar/timegrid": "^5.11.3",
|
||||||
"@fullcalendar/vue3": "^5.11.2",
|
"@fullcalendar/vue3": "^5.11.2",
|
||||||
|
"@iconify/json": "^2.2.156",
|
||||||
|
"@iconify/vue": "^4.1.1",
|
||||||
"@kiwicom/eslint-config": "^12.7.3",
|
"@kiwicom/eslint-config": "^12.7.3",
|
||||||
"@pinia/nuxt": "^0.4.11",
|
"@pinia/nuxt": "^0.4.11",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
@ -54,19 +58,34 @@
|
|||||||
"@vueuse/core": "^9.5.0",
|
"@vueuse/core": "^9.5.0",
|
||||||
"@vueuse/nuxt": "^9.5.0",
|
"@vueuse/nuxt": "^9.5.0",
|
||||||
"apexcharts": "^3.36.0",
|
"apexcharts": "^3.36.0",
|
||||||
|
"archiver": "^6.0.1",
|
||||||
"chart.js": "^3.9.1",
|
"chart.js": "^3.9.1",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
|
"file-type": "^18.7.0",
|
||||||
"floating-vue": "^2.0.0-beta.24",
|
"floating-vue": "^2.0.0-beta.24",
|
||||||
|
"fuse.js": "^7.0.0",
|
||||||
|
"joi": "^17.11.0",
|
||||||
|
"jose": "^5.1.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"jspdf": "^2.5.1",
|
"jspdf": "^2.5.1",
|
||||||
|
"ldapjs": "^3.0.7",
|
||||||
"luxon": "^3.1.0",
|
"luxon": "^3.1.0",
|
||||||
"maska": "^1.5.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",
|
"pinia": "^2.1.6",
|
||||||
"prettier": "^2.8.1",
|
"prettier": "^2.8.1",
|
||||||
"prisma": "^5.1.1",
|
"prisma": "^5.1.1",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
|
"sharp": "^0.32.6",
|
||||||
"swiper": "^8.4.4",
|
"swiper": "^8.4.4",
|
||||||
"thememirror": "^2.0.1",
|
"thememirror": "^2.0.1",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
@ -84,24 +103,7 @@
|
|||||||
"vue3-click-away": "^1.2.4",
|
"vue3-click-away": "^1.2.4",
|
||||||
"vue3-dropzone": "^2.0.1",
|
"vue3-dropzone": "^2.0.1",
|
||||||
"vue3-recaptcha-v2": "^2.0.2",
|
"vue3-recaptcha-v2": "^2.0.2",
|
||||||
"vuedraggable": "^4.1.0",
|
"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"
|
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"luxon": "^3.1.0"
|
"luxon": "^3.1.0"
|
||||||
|
126
pages/test.vue
Normal file
126
pages/test.vue
Normal 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>
|
65
server/api/dms/upload-file.post.js
Normal file
65
server/api/dms/upload-file.post.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
import { readMultipartFormData } from 'h3';
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
// Create S3 client with config
|
||||||
|
const client = new S3Client({
|
||||||
|
region: process.env.NUXT_AWS_REGION,
|
||||||
|
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: "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) {
|
||||||
|
console.error('Error processing request:', error);
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
message: "Failed to generate signed URL",
|
||||||
|
error: error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
67
server/api/test/test-response.post.js
Normal file
67
server/api/test/test-response.post.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user