diff --git a/src/controllers/template.controller.ts b/src/controllers/template.controller.ts new file mode 100644 index 0000000..b74b1f0 --- /dev/null +++ b/src/controllers/template.controller.ts @@ -0,0 +1,103 @@ +import { Response } from 'express'; +import { supabase } from '../lib/supabase'; +import { AuthenticatedRequest } from '../middleware/auth'; + +// 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ığı +// "case-documents" bucket'ı "templates/" öneki altında paylaşılıyor. +export const uploadTemplate = async (req: AuthenticatedRequest, res: Response) => { + try { + const file = req.file; + const userId = req.user?.id; + const { category, name, description } = req.body || {}; + + if (!file || !userId || !name) { + return res.status(400).json({ error: 'file ve name zorunludur.' }); + } + + const storagePath = `templates/${userId}/${Date.now()}_${file.originalname}`; + const { error: uploadError } = await supabase.storage + .from('case-documents') + .upload(storagePath, file.buffer, { contentType: file.mimetype }); + if (uploadError) { + console.error('Template Storage Upload Error:', uploadError); + return res.status(500).json({ error: 'Şablon dosyası yüklenemedi.' }); + } + + const { data, error } = await supabase + .from('templates') + .insert([{ + user_id: userId, + category: category || null, + name: String(name).slice(0, 200), + description: description || null, + storage_path: storagePath, + file_size: file.size, + mime_type: file.mimetype, + }]) + .select() + .single(); + if (error) throw error; + + res.json({ template: data }); + } catch (error: any) { + console.error('Upload Template Error:', error); + res.status(500).json({ error: error.message || 'Internal Server Error' }); + } +}; + +// İndirme için kısa ömürlü bir imzalı URL üretir — bucket public değil. +export const getTemplateDownloadUrl = async (req: AuthenticatedRequest, res: Response) => { + try { + const { templateId } = req.params; + const userId = req.user?.id; + const { data: template, error: fetchError } = await supabase + .from('templates') + .select('storage_path, user_id') + .eq('id', templateId) + .single(); + if (fetchError || !template) { + return res.status(404).json({ error: 'Şablon bulunamadı.' }); + } + if (template.user_id !== userId) { + return res.status(403).json({ error: 'Bu şablona erişim yetkiniz yok.' }); + } + + const { data, error } = await supabase.storage + .from('case-documents') + .createSignedUrl(template.storage_path, 60); + if (error || !data) throw error || new Error('İmzalı URL üretilemedi.'); + + res.json({ url: data.signedUrl }); + } catch (error: any) { + console.error('Get Template Download URL Error:', error); + res.status(500).json({ error: error.message || 'Internal Server Error' }); + } +}; + +export const deleteTemplate = async (req: AuthenticatedRequest, res: Response) => { + try { + const { templateId } = req.params; + const userId = req.user?.id; + const { data: template, error: fetchError } = await supabase + .from('templates') + .select('storage_path, user_id') + .eq('id', templateId) + .single(); + if (fetchError || !template) { + return res.status(404).json({ error: 'Şablon bulunamadı.' }); + } + if (template.user_id !== userId) { + return res.status(403).json({ error: 'Bu şablonu silme yetkiniz yok.' }); + } + + await supabase.storage.from('case-documents').remove([template.storage_path]); + const { error } = await supabase.from('templates').delete().eq('id', templateId); + if (error) throw error; + + res.json({ ok: true }); + } catch (error: any) { + console.error('Delete Template Error:', error); + res.status(500).json({ error: error.message || 'Internal Server Error' }); + } +}; diff --git a/src/routes/index.ts b/src/routes/index.ts index 9945108..52285ab 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -3,6 +3,7 @@ import documentRoutes from './document.routes'; import chatRoutes from './chat.routes'; import precedentRoutes from './precedent.routes'; import caseRoutes from './case.routes'; +import templateRoutes from './template.routes'; const router = Router(); @@ -13,6 +14,7 @@ router.get('/health', (req, res) => { router.use('/documents', documentRoutes); router.use('/chat', chatRoutes); router.use('/cases', caseRoutes); +router.use('/templates', templateRoutes); router.use('/', precedentRoutes); // Keeps the same paths like /api/search-precedents export default router; diff --git a/src/routes/template.routes.ts b/src/routes/template.routes.ts new file mode 100644 index 0000000..0a06ecc --- /dev/null +++ b/src/routes/template.routes.ts @@ -0,0 +1,13 @@ +import { Router } from 'express'; +import multer from 'multer'; +import { requireAuth } from '../middleware/auth'; +import { uploadTemplate, getTemplateDownloadUrl, deleteTemplate } from '../controllers/template.controller'; + +const router = Router(); +const upload = multer({ storage: multer.memoryStorage() }); + +router.post('/upload', upload.single('file'), requireAuth, uploadTemplate); +router.get('/:templateId/download-url', requireAuth, getTemplateDownloadUrl); +router.delete('/:templateId', requireAuth, deleteTemplate); + +export default router;