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 chatRoutes from './chat.routes';
|
||||||
import precedentRoutes from './precedent.routes';
|
import precedentRoutes from './precedent.routes';
|
||||||
import caseRoutes from './case.routes';
|
import caseRoutes from './case.routes';
|
||||||
|
import templateRoutes from './template.routes';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ router.get('/health', (req, res) => {
|
|||||||
router.use('/documents', documentRoutes);
|
router.use('/documents', documentRoutes);
|
||||||
router.use('/chat', chatRoutes);
|
router.use('/chat', chatRoutes);
|
||||||
router.use('/cases', caseRoutes);
|
router.use('/cases', caseRoutes);
|
||||||
|
router.use('/templates', templateRoutes);
|
||||||
router.use('/', precedentRoutes); // Keeps the same paths like /api/search-precedents
|
router.use('/', precedentRoutes); // Keeps the same paths like /api/search-precedents
|
||||||
|
|
||||||
export default router;
|
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