fix: add sanitizePostgresText to filter null bytes and malformed Unicode escape sequences before inserting template text into Supabase

This commit is contained in:
Mustafa Yildiz
2026-08-15 10:20:54 +03:00
parent 95478c09e8
commit 49841241be
2 changed files with 32 additions and 11 deletions
+22 -6
View File
@@ -397,6 +397,20 @@ export const ocrImage = async (req: AuthenticatedRequest, res: Response) => {
} }
}; };
/**
* PostgreSQL ve Supabase JSON/Text alanlarının reddettiği null byte (\u0000),
* bozuk unicode kaçış dizileri ve kontrol karakterlerini temizler.
*/
export function sanitizePostgresText(text: string | null | undefined): string {
if (!text) return '';
return String(text)
.replace(/\0/g, '')
.replace(/\\u0000/gi, '')
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '')
.replace(/[\uD800-\uDFFF]/g, '')
.replace(/\uFFFD/g, '');
}
// uploadDocument ve uploadTemplate'in (template.controller.ts) ortak kullandığı // uploadDocument ve uploadTemplate'in (template.controller.ts) ortak kullandığı
// metin çıkarma adımı: resimler için Tesseract OCR, ofis belgeleri için officeParser. // metin çıkarma adımı: resimler için Tesseract OCR, ofis belgeleri için officeParser.
export async function extractTextFromUploadedFile(file: { buffer: Buffer; mimetype: string; originalname: string }): Promise<string> { export async function extractTextFromUploadedFile(file: { buffer: Buffer; mimetype: string; originalname: string }): Promise<string> {
@@ -444,7 +458,7 @@ export async function extractTextFromUploadedFile(file: { buffer: Buffer; mimety
.replace(/\s+/g, " ") .replace(/\s+/g, " ")
.trim(); .trim();
if (plainText.length > 20) { if (plainText.length > 20) {
return plainText; return sanitizePostgresText(plainText);
} }
} }
} }
@@ -455,7 +469,7 @@ export async function extractTextFromUploadedFile(file: { buffer: Buffer; mimety
const pdfResult: any = await officeParser.parseOffice(pdfBuffer); const pdfResult: any = await officeParser.parseOffice(pdfBuffer);
const pdfText = typeof pdfResult === 'string' ? pdfResult : (pdfResult?.toText ? pdfResult.toText() : String(pdfResult || '')); const pdfText = typeof pdfResult === 'string' ? pdfResult : (pdfResult?.toText ? pdfResult.toText() : String(pdfResult || ''));
if (pdfText && pdfText.trim()) { if (pdfText && pdfText.trim()) {
return pdfText.trim(); return sanitizePostgresText(pdfText.trim());
} }
} }
} }
@@ -464,8 +478,9 @@ export async function extractTextFromUploadedFile(file: { buffer: Buffer; mimety
const rawText = file.buffer.toString('utf8'); const rawText = file.buffer.toString('utf8');
if (rawText && rawText.trim().length > 20) { if (rawText && rawText.trim().length > 20) {
const plainText = rawText.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim(); const plainText = rawText.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
if (plainText.length > 20) { const cleaned = sanitizePostgresText(plainText);
return plainText; if (cleaned.length > 20) {
return cleaned;
} }
} }
@@ -482,14 +497,15 @@ export async function extractTextFromUploadedFile(file: { buffer: Buffer; mimety
imageBuffer = await heicConvert({ buffer: file.buffer, format: 'JPEG', quality: 1 }); imageBuffer = await heicConvert({ buffer: file.buffer, format: 'JPEG', quality: 1 });
} }
const { data: { text } } = await Tesseract.recognize(imageBuffer, 'tur'); const { data: { text } } = await Tesseract.recognize(imageBuffer, 'tur');
return text; return sanitizePostgresText(text);
} }
// officeParser natively supports pdf, docx, doc, rtf, txt, etc. // officeParser natively supports pdf, docx, doc, rtf, txt, etc.
// officeparser@7.x parseOffice() bir sonuc objesi donduruyor (duz string degil), // officeparser@7.x parseOffice() bir sonuc objesi donduruyor (duz string degil),
// duz metin icin .toText() cagirmak gerekiyor. // duz metin icin .toText() cagirmak gerekiyor.
const result: any = await officeParser.parseOffice(file.buffer); const result: any = await officeParser.parseOffice(file.buffer);
return typeof result === 'string' ? result : (result?.toText ? result.toText() : String(result || '')); const rawResult = typeof result === 'string' ? result : (result?.toText ? result.toText() : String(result || ''));
return sanitizePostgresText(rawResult);
} }
// Sohbet ekranlarındaki (genel Sohbet ve dosya bazlı "AyrisLegal'e Sor") ataç // Sohbet ekranlarındaki (genel Sohbet ve dosya bazlı "AyrisLegal'e Sor") ataç
+10 -5
View File
@@ -1,7 +1,7 @@
import { Response } from 'express'; import { Response } from 'express';
import { supabase } from '../lib/supabase'; import { supabase } from '../lib/supabase';
import { AuthenticatedRequest } from '../middleware/auth'; import { AuthenticatedRequest } from '../middleware/auth';
import { extractTextFromUploadedFile } from './document.controller'; import { extractTextFromUploadedFile, sanitizePostgresText } from './document.controller';
// Belge Şablonları: avukatın büro genelinde kullandığı dilekçe/evrak şablonları. // Belge Şablonları: avukatın büro genelinde kullandığı dilekçe/evrak şablonları.
// Ayrı bir Storage bucket açmaya gerek kalmasın diye documents'ın kullandığı // Ayrı bir Storage bucket açmaya gerek kalmasın diye documents'ın kullandığı
@@ -45,17 +45,22 @@ export const uploadTemplate = async (req: AuthenticatedRequest, res: Response) =
return res.status(500).json({ error: 'Şablon dosyası yüklenemedi.' }); return res.status(500).json({ error: 'Şablon dosyası yüklenemedi.' });
} }
const cleanName = sanitizePostgresText(String(name)).slice(0, 200);
const cleanCategory = category ? sanitizePostgresText(String(category)) : null;
const cleanDescription = description ? sanitizePostgresText(String(description)) : null;
const cleanText = extractedText ? sanitizePostgresText(extractedText) : null;
const { data, error } = await supabase const { data, error } = await supabase
.from('templates') .from('templates')
.insert([{ .insert([{
user_id: userId, user_id: userId,
category: category || null, category: cleanCategory,
name: String(name).slice(0, 200), name: cleanName,
description: description || null, description: cleanDescription,
storage_path: storagePath, storage_path: storagePath,
file_size: file.size, file_size: file.size,
mime_type: file.mimetype, mime_type: file.mimetype,
extracted_text: extractedText, extracted_text: cleanText,
}]) }])
.select() .select()
.single(); .single();