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:
mstfyldz
2026-08-09 19:50:46 +03:00
co-authored by Claude Sonnet 5
parent 99d4d770d5
commit af5a03db92
3 changed files with 118 additions and 0 deletions
+103
View File
@@ -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' });
}
};