feat: add template upload/download/delete endpoints
Belge Şablonları now stores real files: POST /api/templates/upload (multer -> Supabase Storage under templates/<user>/, then a templates row), GET /api/templates/:id/download-url (short-lived signed URL), and DELETE /api/templates/:id. Mounted at /api/templates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
99d4d770d5
commit
af5a03db92
@@ -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' });
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user