Compare commits
No commits in common. "6e9f37c1cdd7e600ad579415d77a9a2992d2cdd8" and "8aa9a9b08221a4faa6ee1119ad390cc8d487a31c" have entirely different histories.
6e9f37c1cd
...
8aa9a9b082
File diff suppressed because one or more lines are too long
@ -142,7 +142,6 @@ WantedBy=multi-user.target
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name forensic-pathways.yourdomain.com;
|
server_name forensic-pathways.yourdomain.com;
|
||||||
client_max_body_size 50M; # Important for uploads
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:4321;
|
proxy_pass http://localhost:4321;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// src/pages/api/upload/media.ts - Enhanced with detailed logging and error handling
|
// src/pages/api/upload/media.ts
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { withAPIAuth } from '../../../utils/auth.js';
|
import { withAPIAuth } from '../../../utils/auth.js';
|
||||||
import { apiResponse, apiError, apiServerError, apiSpecial, handleAPIRequest } from '../../../utils/api.js';
|
import { apiResponse, apiError, apiServerError, apiSpecial, handleAPIRequest } from '../../../utils/api.js';
|
||||||
@ -21,13 +21,8 @@ interface UploadResult {
|
|||||||
const UPLOAD_CONFIG = {
|
const UPLOAD_CONFIG = {
|
||||||
maxFileSize: 50 * 1024 * 1024, // 50MB
|
maxFileSize: 50 * 1024 * 1024, // 50MB
|
||||||
allowedTypes: new Set([
|
allowedTypes: new Set([
|
||||||
// Images
|
|
||||||
'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
|
'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
|
||||||
|
|
||||||
// Videos
|
|
||||||
'video/mp4', 'video/webm', 'video/ogg', 'video/avi', 'video/mov',
|
'video/mp4', 'video/webm', 'video/ogg', 'video/avi', 'video/mov',
|
||||||
|
|
||||||
// Documents
|
|
||||||
'application/pdf',
|
'application/pdf',
|
||||||
'application/msword',
|
'application/msword',
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
@ -35,32 +30,7 @@ const UPLOAD_CONFIG = {
|
|||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
'application/vnd.ms-powerpoint',
|
'application/vnd.ms-powerpoint',
|
||||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'text/plain', 'text/csv', 'application/json'
|
||||||
// Text files
|
|
||||||
'text/plain',
|
|
||||||
'text/csv',
|
|
||||||
'text/markdown', // Added markdown
|
|
||||||
'text/x-markdown', // Alternative markdown MIME type
|
|
||||||
'application/json',
|
|
||||||
'application/xml',
|
|
||||||
'text/xml',
|
|
||||||
'text/html',
|
|
||||||
|
|
||||||
// Archives
|
|
||||||
'application/zip',
|
|
||||||
'application/x-tar',
|
|
||||||
'application/gzip',
|
|
||||||
'application/x-gzip',
|
|
||||||
'application/x-zip-compressed',
|
|
||||||
'application/x-rar-compressed',
|
|
||||||
'application/x-7z-compressed',
|
|
||||||
|
|
||||||
// Additional useful formats
|
|
||||||
'application/rtf', // Rich Text Format
|
|
||||||
'text/richtext',
|
|
||||||
'application/x-yaml', // YAML files
|
|
||||||
'text/yaml',
|
|
||||||
'application/yaml'
|
|
||||||
]),
|
]),
|
||||||
localUploadPath: process.env.LOCAL_UPLOAD_PATH || './public/uploads',
|
localUploadPath: process.env.LOCAL_UPLOAD_PATH || './public/uploads',
|
||||||
publicBaseUrl: process.env.PUBLIC_BASE_URL || 'http://localhost:4321'
|
publicBaseUrl: process.env.PUBLIC_BASE_URL || 'http://localhost:4321'
|
||||||
@ -80,7 +50,6 @@ function checkUploadRateLimit(userEmail: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userLimit.count >= RATE_LIMIT_MAX) {
|
if (userLimit.count >= RATE_LIMIT_MAX) {
|
||||||
console.warn(`[UPLOAD] Rate limit exceeded for user: ${userEmail} (${userLimit.count}/${RATE_LIMIT_MAX})`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,37 +58,27 @@ function checkUploadRateLimit(userEmail: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateFile(file: File): { valid: boolean; error?: string } {
|
function validateFile(file: File): { valid: boolean; error?: string } {
|
||||||
console.log(`[UPLOAD] Validating file: ${file.name}, size: ${file.size}, type: ${file.type}`);
|
|
||||||
|
|
||||||
if (file.size > UPLOAD_CONFIG.maxFileSize) {
|
if (file.size > UPLOAD_CONFIG.maxFileSize) {
|
||||||
const errorMsg = `File too large. Maximum size is ${Math.round(UPLOAD_CONFIG.maxFileSize / 1024 / 1024)}MB`;
|
return {
|
||||||
console.warn(`[UPLOAD] ${errorMsg} - File size: ${file.size}`);
|
valid: false,
|
||||||
return { valid: false, error: errorMsg };
|
error: `File too large. Maximum size is ${Math.round(UPLOAD_CONFIG.maxFileSize / 1024 / 1024)}MB`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UPLOAD_CONFIG.allowedTypes.has(file.type)) {
|
if (!UPLOAD_CONFIG.allowedTypes.has(file.type)) {
|
||||||
const errorMsg = `File type ${file.type} not allowed`;
|
return {
|
||||||
console.warn(`[UPLOAD] ${errorMsg} - Allowed types:`, Array.from(UPLOAD_CONFIG.allowedTypes));
|
valid: false,
|
||||||
return { valid: false, error: errorMsg };
|
error: `File type ${file.type} not allowed`
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[UPLOAD] File validation passed for: ${file.name}`);
|
|
||||||
return { valid: true };
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadToNextcloud(file: File, userEmail: string): Promise<UploadResult> {
|
async function uploadToNextcloud(file: File, userEmail: string): Promise<UploadResult> {
|
||||||
console.log(`[UPLOAD] Attempting Nextcloud upload for: ${file.name} by ${userEmail}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uploader = new NextcloudUploader();
|
const uploader = new NextcloudUploader();
|
||||||
const result = await uploader.uploadFile(file, userEmail);
|
const result = await uploader.uploadFile(file, userEmail);
|
||||||
|
|
||||||
console.log(`[UPLOAD] Nextcloud upload successful:`, {
|
|
||||||
filename: result.filename,
|
|
||||||
url: result.url,
|
|
||||||
size: file.size
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
url: result.url,
|
url: result.url,
|
||||||
@ -128,7 +87,7 @@ async function uploadToNextcloud(file: File, userEmail: string): Promise<UploadR
|
|||||||
storage: 'nextcloud'
|
storage: 'nextcloud'
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UPLOAD] Nextcloud upload failed:', error);
|
console.error('Nextcloud upload failed:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'Nextcloud upload failed',
|
error: error instanceof Error ? error.message : 'Nextcloud upload failed',
|
||||||
@ -138,10 +97,7 @@ async function uploadToNextcloud(file: File, userEmail: string): Promise<UploadR
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadToLocal(file: File, userType: string): Promise<UploadResult> {
|
async function uploadToLocal(file: File, userType: string): Promise<UploadResult> {
|
||||||
console.log(`[UPLOAD] Attempting local upload for: ${file.name} (${userType})`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[UPLOAD] Creating directory: ${UPLOAD_CONFIG.localUploadPath}`);
|
|
||||||
await fs.mkdir(UPLOAD_CONFIG.localUploadPath, { recursive: true });
|
await fs.mkdir(UPLOAD_CONFIG.localUploadPath, { recursive: true });
|
||||||
|
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
@ -150,20 +106,11 @@ async function uploadToLocal(file: File, userType: string): Promise<UploadResult
|
|||||||
const filename = `${timestamp}-${randomString}${extension}`;
|
const filename = `${timestamp}-${randomString}${extension}`;
|
||||||
|
|
||||||
const filepath = path.join(UPLOAD_CONFIG.localUploadPath, filename);
|
const filepath = path.join(UPLOAD_CONFIG.localUploadPath, filename);
|
||||||
console.log(`[UPLOAD] Writing file to: ${filepath}`);
|
|
||||||
|
|
||||||
const buffer = Buffer.from(await file.arrayBuffer());
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
await fs.writeFile(filepath, buffer);
|
await fs.writeFile(filepath, buffer);
|
||||||
|
|
||||||
const publicUrl = `${UPLOAD_CONFIG.publicBaseUrl}/uploads/${filename}`;
|
const publicUrl = `${UPLOAD_CONFIG.publicBaseUrl}/uploads/${filename}`;
|
||||||
|
|
||||||
console.log(`[UPLOAD] Local upload successful:`, {
|
|
||||||
filename,
|
|
||||||
filepath,
|
|
||||||
publicUrl,
|
|
||||||
size: file.size
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
url: publicUrl,
|
url: publicUrl,
|
||||||
@ -172,7 +119,7 @@ async function uploadToLocal(file: File, userType: string): Promise<UploadResult
|
|||||||
storage: 'local'
|
storage: 'local'
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UPLOAD] Local upload failed:', error);
|
console.error('Local upload failed:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'Local upload failed',
|
error: error instanceof Error ? error.message : 'Local upload failed',
|
||||||
@ -183,23 +130,12 @@ async function uploadToLocal(file: File, userType: string): Promise<UploadResult
|
|||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
return await handleAPIRequest(async () => {
|
return await handleAPIRequest(async () => {
|
||||||
console.log('[UPLOAD] Processing upload request');
|
|
||||||
|
|
||||||
// Enhanced auth logging
|
|
||||||
const authResult = await withAPIAuth(request, 'contributions');
|
const authResult = await withAPIAuth(request, 'contributions');
|
||||||
console.log('[UPLOAD] Auth result:', {
|
|
||||||
authenticated: authResult.authenticated,
|
|
||||||
authRequired: authResult.authRequired,
|
|
||||||
userId: authResult.userId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authResult.authRequired && !authResult.authenticated) {
|
if (authResult.authRequired && !authResult.authenticated) {
|
||||||
console.warn('[UPLOAD] Upload rejected - authentication required but user not authenticated');
|
return apiError.unauthorized();
|
||||||
return apiError.unauthorized('Authentication required for file uploads');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userEmail = authResult.session?.email || 'anon@anon.anon';
|
const userEmail = authResult.session?.email || 'anon@anon.anon';
|
||||||
console.log(`[UPLOAD] Processing upload for user: ${userEmail}`);
|
|
||||||
|
|
||||||
if (!checkUploadRateLimit(userEmail)) {
|
if (!checkUploadRateLimit(userEmail)) {
|
||||||
return apiError.rateLimit('Upload rate limit exceeded. Please wait before uploading again.');
|
return apiError.rateLimit('Upload rate limit exceeded. Please wait before uploading again.');
|
||||||
@ -207,60 +143,38 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
let formData;
|
let formData;
|
||||||
try {
|
try {
|
||||||
console.log('[UPLOAD] Parsing form data');
|
|
||||||
formData = await request.formData();
|
formData = await request.formData();
|
||||||
console.log('[UPLOAD] Form data keys:', Array.from(formData.keys()));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UPLOAD] Failed to parse form data:', error);
|
return apiError.badRequest('Invalid form data');
|
||||||
return apiError.badRequest('Invalid form data - could not parse request');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = formData.get('file') as File;
|
const file = formData.get('file') as File;
|
||||||
const type = formData.get('type') as string;
|
const type = formData.get('type') as string;
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
console.warn('[UPLOAD] No file provided in request');
|
|
||||||
return apiSpecial.missingRequired(['file']);
|
return apiSpecial.missingRequired(['file']);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[UPLOAD] Processing file: ${file.name}, type parameter: ${type}`);
|
|
||||||
|
|
||||||
const validation = validateFile(file);
|
const validation = validateFile(file);
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
return apiError.badRequest(validation.error!);
|
return apiError.badRequest(validation.error!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced environment logging
|
|
||||||
const nextcloudConfigured = isNextcloudConfigured();
|
|
||||||
console.log('[UPLOAD] Environment check:', {
|
|
||||||
nextcloudConfigured,
|
|
||||||
localUploadPath: UPLOAD_CONFIG.localUploadPath,
|
|
||||||
publicBaseUrl: UPLOAD_CONFIG.publicBaseUrl,
|
|
||||||
nodeEnv: process.env.NODE_ENV
|
|
||||||
});
|
|
||||||
|
|
||||||
let result: UploadResult;
|
let result: UploadResult;
|
||||||
|
|
||||||
if (nextcloudConfigured) {
|
if (isNextcloudConfigured()) {
|
||||||
console.log('[UPLOAD] Using Nextcloud as primary storage');
|
|
||||||
result = await uploadToNextcloud(file, userEmail);
|
result = await uploadToNextcloud(file, userEmail);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
console.warn('[UPLOAD] Nextcloud upload failed, trying local fallback:', result.error);
|
console.warn('Nextcloud upload failed, trying local fallback:', result.error);
|
||||||
result = await uploadToLocal(file, type);
|
result = await uploadToLocal(file, type);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('[UPLOAD] Using local storage (Nextcloud not configured)');
|
|
||||||
result = await uploadToLocal(file, type);
|
result = await uploadToLocal(file, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log(`[UPLOAD] Upload completed successfully:`, {
|
console.log(`[MEDIA UPLOAD] ${file.name} (${file.size} bytes) by ${userEmail} -> ${result.storage}: ${result.url}`);
|
||||||
filename: result.filename,
|
|
||||||
storage: result.storage,
|
|
||||||
url: result.url,
|
|
||||||
user: userEmail
|
|
||||||
});
|
|
||||||
|
|
||||||
return apiSpecial.uploadSuccess({
|
return apiSpecial.uploadSuccess({
|
||||||
url: result.url!,
|
url: result.url!,
|
||||||
@ -269,12 +183,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
storage: result.storage!
|
storage: result.storage!
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(`[UPLOAD] Upload failed completely:`, {
|
console.error(`[MEDIA UPLOAD FAILED] ${file.name} by ${userEmail}: ${result.error}`);
|
||||||
filename: file.name,
|
|
||||||
error: result.error,
|
|
||||||
storage: result.storage,
|
|
||||||
user: userEmail
|
|
||||||
});
|
|
||||||
|
|
||||||
return apiSpecial.uploadFailed(result.error!);
|
return apiSpecial.uploadFailed(result.error!);
|
||||||
}
|
}
|
||||||
@ -284,8 +193,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
export const GET: APIRoute = async ({ request }) => {
|
export const GET: APIRoute = async ({ request }) => {
|
||||||
return await handleAPIRequest(async () => {
|
return await handleAPIRequest(async () => {
|
||||||
console.log('[UPLOAD] Getting upload status');
|
|
||||||
|
|
||||||
const authResult = await withAPIAuth(request);
|
const authResult = await withAPIAuth(request);
|
||||||
if (authResult.authRequired && !authResult.authenticated) {
|
if (authResult.authRequired && !authResult.authenticated) {
|
||||||
return apiError.unauthorized();
|
return apiError.unauthorized();
|
||||||
@ -297,14 +204,12 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
try {
|
try {
|
||||||
await fs.access(UPLOAD_CONFIG.localUploadPath);
|
await fs.access(UPLOAD_CONFIG.localUploadPath);
|
||||||
localStorageAvailable = true;
|
localStorageAvailable = true;
|
||||||
console.log('[UPLOAD] Local storage accessible');
|
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
await fs.mkdir(UPLOAD_CONFIG.localUploadPath, { recursive: true });
|
await fs.mkdir(UPLOAD_CONFIG.localUploadPath, { recursive: true });
|
||||||
localStorageAvailable = true;
|
localStorageAvailable = true;
|
||||||
console.log('[UPLOAD] Local storage created');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[UPLOAD] Local upload directory not accessible:', error);
|
console.warn('Local upload directory not accessible:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,14 +237,9 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
paths: {
|
paths: {
|
||||||
uploadEndpoint: '/api/upload/media',
|
uploadEndpoint: '/api/upload/media',
|
||||||
localPath: localStorageAvailable ? '/uploads' : null
|
localPath: localStorageAvailable ? '/uploads' : null
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
nodeEnv: process.env.NODE_ENV,
|
|
||||||
publicBaseUrl: UPLOAD_CONFIG.publicBaseUrl
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[UPLOAD] Status check completed:', status);
|
|
||||||
return apiResponse.success(status);
|
return apiResponse.success(status);
|
||||||
|
|
||||||
}, 'Upload status retrieval failed');
|
}, 'Upload status retrieval failed');
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
// src/pages/contribute/knowledgebase.astro
|
// src/pages/contribute/knowledgebase.astro - SIMPLIFIED: Issues only, minimal validation
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
import { withAuth } from '../../utils/auth.js';
|
import { withAuth } from '../../utils/auth.js';
|
||||||
import { getToolsData } from '../../utils/dataService.js';
|
import { getToolsData } from '../../utils/dataService.js';
|
||||||
@ -114,13 +114,8 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Dokumente, Bilder, Videos (Optional)</label>
|
<label class="form-label">Dokumente, Bilder, Videos (Optional)</label>
|
||||||
<div class="upload-area" id="upload-area">
|
<div class="upload-area" id="upload-area">
|
||||||
<input
|
<input type="file" id="file-input" multiple accept=".pdf,.doc,.docx,.txt,.md,.zip,.png,.jpg,.jpeg,.gif,.mp4,.webm" class="hidden">
|
||||||
type="file"
|
<div class="upload-placeholder">
|
||||||
id="file-input"
|
|
||||||
multiple
|
|
||||||
accept=".pdf,.doc,.docx,.txt,.md,.markdown,.csv,.json,.xml,.html,.rtf,.yaml,.yml,.zip,.tar,.gz,.rar,.7z,.png,.jpg,.jpeg,.gif,.webp,.svg,.mp4,.webm,.mov,.avi"
|
|
||||||
class="hidden"
|
|
||||||
> <div class="upload-placeholder">
|
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||||
<polyline points="7 10 12 15 17 10"/>
|
<polyline points="7 10 12 15 17 10"/>
|
||||||
@ -309,13 +304,6 @@ class KnowledgebaseForm {
|
|||||||
|
|
||||||
private handleFiles(files: File[]) {
|
private handleFiles(files: File[]) {
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
// Client-side validation before upload
|
|
||||||
const validation = this.validateFileBeforeUpload(file);
|
|
||||||
if (!validation.valid) {
|
|
||||||
console.log('[UPLOAD]Cannot upload ', file.name, ' Error: ', validation.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
const fileId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||||
const newFile: UploadedFile = {
|
const newFile: UploadedFile = {
|
||||||
id: fileId,
|
id: fileId,
|
||||||
@ -329,99 +317,30 @@ class KnowledgebaseForm {
|
|||||||
this.renderFileList();
|
this.renderFileList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private validateFileBeforeUpload(file: File): { valid: boolean; error?: string } {
|
|
||||||
const maxSizeBytes = 50 * 1024 * 1024; // 50MB
|
|
||||||
if (file.size > maxSizeBytes) {
|
|
||||||
const sizeMB = (file.size / 1024 / 1024).toFixed(1);
|
|
||||||
const maxMB = (maxSizeBytes / 1024 / 1024).toFixed(0);
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
error: `File too large (${sizeMB}MB). Maximum size: ${maxMB}MB`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check file type
|
|
||||||
const allowedExtensions = [
|
|
||||||
'.pdf', '.doc', '.docx', '.txt', '.md', '.markdown', '.csv', '.json',
|
|
||||||
'.xml', '.html', '.rtf', '.yaml', '.yml', '.zip', '.tar', '.gz',
|
|
||||||
'.rar', '.7z', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg',
|
|
||||||
'.mp4', '.webm', '.mov', '.avi'
|
|
||||||
];
|
|
||||||
|
|
||||||
const fileName = file.name.toLowerCase();
|
|
||||||
const hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext));
|
|
||||||
|
|
||||||
if (!hasValidExtension) {
|
|
||||||
return {
|
|
||||||
valid: false,
|
|
||||||
error: `File type not allowed. Allowed: ${allowedExtensions.join(', ')}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async uploadFile(fileId: string) {
|
private async uploadFile(fileId: string) {
|
||||||
const fileItem = this.uploadedFiles.find(f => f.id === fileId);
|
const fileItem = this.uploadedFiles.find(f => f.id === fileId);
|
||||||
if (!fileItem) {
|
if (!fileItem) return;
|
||||||
console.error('[UPLOAD] File item not found for ID:', fileId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[UPLOAD] Starting upload for:', fileItem.name, 'Size:', fileItem.file.size, 'Type:', fileItem.file.type);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', fileItem.file);
|
formData.append('file', fileItem.file);
|
||||||
formData.append('type', 'knowledgebase');
|
formData.append('type', 'knowledgebase');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('[UPLOAD] Sending request to /api/upload/media');
|
|
||||||
|
|
||||||
const response = await fetch('/api/upload/media', {
|
const response = await fetch('/api/upload/media', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[UPLOAD] Response status:', response.status);
|
|
||||||
|
|
||||||
let responseText: string;
|
|
||||||
let responseData: any;
|
|
||||||
|
|
||||||
try {
|
|
||||||
responseText = await response.text();
|
|
||||||
console.log('[UPLOAD] Raw response:', responseText.substring(0, 200));
|
|
||||||
|
|
||||||
try {
|
|
||||||
responseData = JSON.parse(responseText);
|
|
||||||
} catch (parseError) {
|
|
||||||
responseData = { error: responseText };
|
|
||||||
}
|
|
||||||
} catch (readError) {
|
|
||||||
console.error('[UPLOAD] Failed to read response:', readError);
|
|
||||||
throw new Error('Failed to read server response');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log('[UPLOAD] Success result:', responseData);
|
const result = await response.json();
|
||||||
|
|
||||||
fileItem.uploaded = true;
|
fileItem.uploaded = true;
|
||||||
fileItem.url = responseData.url;
|
fileItem.url = result.url;
|
||||||
this.renderFileList();
|
this.renderFileList();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
throw new Error('Upload failed');
|
||||||
if (responseData && responseData.details) {
|
|
||||||
console.error('[UPLOAD] Error details:', responseData.details);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UPLOAD] Upload error for', fileItem.name, ':', error);
|
this.showMessage('error', `Failed to upload ${fileItem.name}`);
|
||||||
|
|
||||||
const errorMessage = error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: 'Unknown upload error';
|
|
||||||
|
|
||||||
this.removeFile(fileId);
|
this.removeFile(fileId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,6 +412,7 @@ class KnowledgebaseForm {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[KB FORM] Submission error:', error);
|
console.error('[KB FORM] Submission error:', error);
|
||||||
|
this.showMessage('error', 'Submission failed. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
this.isSubmitting = false;
|
this.isSubmitting = false;
|
||||||
(this.elements.submitBtn as HTMLButtonElement).disabled = false;
|
(this.elements.submitBtn as HTMLButtonElement).disabled = false;
|
||||||
@ -521,6 +441,18 @@ class KnowledgebaseForm {
|
|||||||
this.renderFileList();
|
this.renderFileList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showMessage(type: 'success' | 'error' | 'warning', message: string) {
|
||||||
|
const container = document.getElementById('message-container');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const messageEl = document.createElement('div');
|
||||||
|
messageEl.className = `message message-${type}`;
|
||||||
|
messageEl.textContent = message;
|
||||||
|
|
||||||
|
container.appendChild(messageEl);
|
||||||
|
setTimeout(() => messageEl.remove(), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
public removeFileById(fileId: string) {
|
public removeFileById(fileId: string) {
|
||||||
this.removeFile(fileId);
|
this.removeFile(fileId);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user