8ccaa824dd
- Add HMAC-based file token auth for secure AI model file access - Add file upload retry with exponential backoff (max 3 retries) - File endpoint now requires session auth or signed token - Support assistant role messages in chat input - Optimistic UI for attachments on message send - Verify topic ownership before allowing messages - Switch web scraping to Firecrawl API - Agent profile page layout fixes (proper flex overflow) - Add quick switcher (Ctrl+K) to sidenav - Clean up longcat.ts and stale comments
88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
import * as z from 'zod';
|
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
await protectRoute(event);
|
|
|
|
const result = await readValidatedBody(event, (body) =>
|
|
z
|
|
.object({
|
|
file: z.object({
|
|
name: z.string(),
|
|
mimeType: z.string(),
|
|
}),
|
|
})
|
|
.safeParse(body),
|
|
);
|
|
|
|
if (!result.success) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: result.error.issues[0]!.message,
|
|
});
|
|
}
|
|
|
|
const config = useRuntimeConfig();
|
|
|
|
const { file } = result.data;
|
|
|
|
const s3 = new S3Client({
|
|
region: config.s3.region || 'us-east-1',
|
|
endpoint: config.s3.endpoint!,
|
|
credentials: {
|
|
accessKeyId: config.s3.accessKeyId!,
|
|
secretAccessKey: config.s3.secret!,
|
|
},
|
|
forcePathStyle: true,
|
|
requestChecksumCalculation: 'WHEN_REQUIRED',
|
|
responseChecksumValidation: 'WHEN_REQUIRED',
|
|
});
|
|
|
|
const fileParts = file.name.split('.');
|
|
if (fileParts.length < 2) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Invalid file name',
|
|
data: {
|
|
code: 'INVALID_FILE_NAME',
|
|
ok: false,
|
|
}
|
|
});
|
|
}
|
|
|
|
const fileExt = fileParts.pop()!;
|
|
const fileName = fileParts.join('.').replace(/[^a-zA-Z0-9.]/g, '_');
|
|
|
|
const key = `veridian__uploads/${Date.now()}-${fileName}-${event.context.user!.id}.${fileExt}`;
|
|
|
|
const command = new PutObjectCommand({
|
|
ACL: 'public-read',
|
|
Bucket: config.s3.bucket!,
|
|
Key: key,
|
|
ContentType: file.mimeType,
|
|
});
|
|
|
|
try {
|
|
const url = await getSignedUrl(s3, command, {
|
|
expiresIn: 600,
|
|
signableHeaders: new Set(['content-type']),
|
|
});
|
|
|
|
return {
|
|
url,
|
|
assetUrl: `/api/files/${key}`,
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to generate presigned URL:', error);
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Failed to generate presigned URL',
|
|
data: {
|
|
code: 'INTERNAL_ERROR',
|
|
ok: false,
|
|
}
|
|
});
|
|
}
|
|
})
|