Optimized the root .gitignore to exclude virtual environments, node modules, and temp folders to ensure clean and lightweight version tracking. Co-authored-by: Cursor <cursoragent@cursor.com>
88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import type { Plugin } from 'vite';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import { getRequestPathname } from './utils/httpUtils';
|
|
|
|
export function uploadDocsApiPlugin(): Plugin {
|
|
return {
|
|
name: 'upload-docs-api-plugin',
|
|
configureServer(server: any) {
|
|
server.middlewares.use((req: any, res: any, next: any) => {
|
|
const pathname = getRequestPathname(req);
|
|
if (req.method !== 'POST' || pathname !== '/api/upload-docs') {
|
|
return next();
|
|
}
|
|
|
|
const chunks: Buffer[] = [];
|
|
let totalLength = 0;
|
|
|
|
req.on('data', (chunk: Buffer) => {
|
|
totalLength += chunk.length;
|
|
if (totalLength > 1024 * 1024 * 20) {
|
|
res.statusCode = 413;
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.end(JSON.stringify({ error: 'Payload too large' }));
|
|
req.destroy();
|
|
return;
|
|
}
|
|
chunks.push(chunk);
|
|
});
|
|
|
|
req.on('end', () => {
|
|
try {
|
|
const raw = Buffer.concat(chunks).toString('utf8');
|
|
const body = raw ? JSON.parse(raw) : null;
|
|
const files = body?.files;
|
|
|
|
if (!Array.isArray(files) || files.length === 0) {
|
|
res.statusCode = 400;
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.end(JSON.stringify({ error: 'Missing files' }));
|
|
return;
|
|
}
|
|
|
|
const docsDir = path.resolve(process.cwd(), 'src/docs');
|
|
fs.mkdirSync(docsDir, { recursive: true });
|
|
|
|
const saved: string[] = [];
|
|
|
|
files.forEach((f: any) => {
|
|
const rawName = typeof f?.name === 'string' ? f.name : '';
|
|
const content = typeof f?.content === 'string' ? f.content : '';
|
|
|
|
if (!rawName) {
|
|
throw new Error('Invalid file name');
|
|
}
|
|
|
|
let safeName = path.basename(rawName).trim();
|
|
safeName = safeName.replace(/[^\w.\- ]+/g, '-').replace(/\s+/g, '-');
|
|
|
|
const lowerName = safeName.toLowerCase();
|
|
if (!lowerName.endsWith('.md') && !lowerName.endsWith('.csv') && !lowerName.endsWith('.json')) {
|
|
throw new Error('Only .md, .csv, and .json files are allowed');
|
|
}
|
|
|
|
const targetPath = path.join(docsDir, safeName);
|
|
if (!targetPath.startsWith(docsDir)) {
|
|
throw new Error('Forbidden');
|
|
}
|
|
|
|
fs.writeFileSync(targetPath, content, 'utf8');
|
|
saved.push(safeName.replace(/\.(md|csv|json)$/i, ''));
|
|
});
|
|
|
|
res.statusCode = 200;
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.end(JSON.stringify({ success: true, files: saved }));
|
|
} catch (error: any) {
|
|
res.statusCode = 500;
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.end(JSON.stringify({ error: error?.message || 'Upload failed' }));
|
|
}
|
|
});
|
|
});
|
|
},
|
|
};
|
|
}
|