Fix cartazes deletion and media persistence

This commit is contained in:
2026-06-07 14:25:25 +00:00
parent c4c8e6ffcf
commit 00bd9ecace
2 changed files with 108 additions and 16 deletions
+8 -8
View File
@@ -4112,7 +4112,7 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
document.querySelectorAll('.btn-ver-estudio').forEach(btn => {
btn.addEventListener('click', async (e) => {
const id = e.target.dataset.id;
const id = e.currentTarget.dataset.id;
try {
const res = await fetch(`/api/music/${id}`);
const itemData = await res.json();
@@ -4130,10 +4130,10 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
document.querySelectorAll('.btn-del-estudio').forEach(btn => {
btn.addEventListener('click', async (e) => {
if(!confirm('Tem certeza que deseja apagar esta canção?')) return;
const id = e.target.dataset.id;
const id = e.currentTarget.dataset.id;
try {
await fetch(`/api/music/${id}`, { method: 'DELETE' });
e.target.closest('div[style*="var(--bg-tertiary)"]').remove();
e.currentTarget.closest('div[style*="var(--bg-tertiary)"]').remove();
} catch (err) { alert('Erro ao deletar.'); }
});
});
@@ -6800,7 +6800,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.btn-ver-mindlab').forEach(btn => {
btn.addEventListener('click', async (e) => {
const id = e.target.dataset.id;
const id = e.currentTarget.dataset.id;
try {
const res = await fetch(`/api/mindlab/${id}`, {
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
@@ -6820,13 +6820,13 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.btn-del-mindlab').forEach(btn => {
btn.addEventListener('click', async (e) => {
if(!confirm('Tem certeza que deseja apagar este planejamento?')) return;
const id = e.target.dataset.id;
const id = e.currentTarget.dataset.id;
try {
await fetch(`/api/mindlab/${id}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
});
e.target.closest('div[style*="var(--bg-tertiary)"]').remove();
e.currentTarget.closest('div[style*="var(--bg-tertiary)"]').remove();
} catch (err) { alert('Erro ao deletar.'); }
});
});
@@ -7188,7 +7188,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Bind Ver/Editar Buttons
document.querySelectorAll('.btn-ver-cartaz').forEach(btn => {
btn.addEventListener('click', async (e) => {
const id = e.target.dataset.id;
const id = e.currentTarget.dataset.id;
try {
const r = await fetch(`/api/cartazes/${id}`, {
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
@@ -7223,7 +7223,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.btn-del-cartaz').forEach(btn => {
btn.addEventListener('click', async (e) => {
if (!confirm('Tem certeza que deseja apagar este cartaz?')) return;
const id = e.target.dataset.id;
const id = e.currentTarget.dataset.id;
try {
const r = await fetch(`/api/cartazes/${id}`, {
method: 'DELETE',
+100 -8
View File
@@ -82,6 +82,30 @@ const dbPool = new Pool({
connectionString: process.env.DATABASE_URL
});
async function backupMediaFile(filePath, buffer) {
try {
const filename = path.basename(filePath);
let contentType = 'application/octet-stream';
const ext = path.extname(filename).toLowerCase();
if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg';
else if (ext === '.png') contentType = 'image/png';
else if (ext === '.gif') contentType = 'image/gif';
else if (ext === '.mp3') contentType = 'audio/mpeg';
else if (ext === '.mp4') contentType = 'video/mp4';
else if (ext === '.wav') contentType = 'audio/wav';
await dbPool.query(
`INSERT INTO escola.arquivos (filename, content_type, dados)
VALUES ($1, $2, $3)
ON CONFLICT (filename) DO UPDATE SET dados = $3, content_type = $2`,
[filename, contentType, buffer]
);
console.log(`[Media Backup] Backup do arquivo ${filename} salvo no banco escola.arquivos.`);
} catch (err) {
console.error(`[Media Backup] Erro ao fazer backup do arquivo ${filePath}:`, err);
}
}
const CONFIG_FILE_PATH = path.join(__dirname, 'agent_config.json');
const KNOWLEDGE_FILE_PATH = path.join(__dirname, 'conhecimento_adquirido.json');
const TEMPERATURE_PRESETS = {
@@ -302,7 +326,9 @@ Retorne APENAS a letra formatada em versos com quebras de linha. Não adicione n
}
const fileName = `estudio_musica_${Date.now()}.mp3`;
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, Buffer.from(audioHex, 'hex'));
const audioBuffer = Buffer.from(audioHex, 'hex');
fs.writeFileSync(filePath, audioBuffer);
await backupMediaFile(filePath, audioBuffer);
const audioUrl = `/generated-media/${fileName}`;
// 6. Retornar dados para o frontend
@@ -526,6 +552,7 @@ app.post('/api/comics/generate-frame', requireAuth, async (req, res) => {
const fileName = `quadrinho_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, imgBuffer);
await backupMediaFile(filePath, imgBuffer);
res.json({ imageUrl: `/generated-media/${fileName}` });
} catch (error) {
@@ -600,6 +627,16 @@ app.post('/api/comics/generate-video', requireAuth, async (req, res) => {
return res.status(500).json({ error: 'Erro ao gerar o vídeo da apresentação. Como o servidor foi atualizado e reiniciado recentemente, por favor crie/gere a história em quadrinhos novamente para recriar as imagens temporárias antes de exportar o vídeo.' });
}
// Backup do vídeo gerado no banco de dados
(async () => {
try {
const videoBuffer = fs.readFileSync(outputFilePath);
await backupMediaFile(outputFilePath, videoBuffer);
} catch (backupErr) {
console.error('[Media Backup] Erro ao fazer backup do vídeo do FFmpeg:', backupErr);
}
})();
res.json({ videoUrl: `/generated-media/${outputFileName}` });
});
@@ -909,6 +946,7 @@ app.post('/api/garatujas/analyze', requireAuth, multerUpload.single('image'), as
const fileName = `garatuja_${Date.now()}${ext}`;
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, file.buffer);
await backupMediaFile(filePath, file.buffer);
const imageUrl = `/generated-media/${fileName}`;
const base64Image = file.buffer.toString('base64');
@@ -1448,7 +1486,9 @@ REGRAS:
if (imgFetch.ok) {
const imgBuffer = Buffer.from(await imgFetch.arrayBuffer());
const fileName = `cartaz_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
fs.writeFileSync(path.join(mediaDir, fileName), imgBuffer);
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, imgBuffer);
await backupMediaFile(filePath, imgBuffer);
generatedImageUrl = `/generated-media/${fileName}`;
}
}
@@ -2063,6 +2103,15 @@ async function initDatabase() {
imagem_url TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Arquivos persistentes (Mídia gerada por IA)
CREATE TABLE IF NOT EXISTS escola.arquivos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename VARCHAR(255) UNIQUE NOT NULL,
content_type VARCHAR(100) NOT NULL,
dados BYTEA NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`);
// Garantir que a coluna character_description_english exista na tabela projetos_comics
@@ -3229,13 +3278,51 @@ app.use((req, res, next) => {
req.path === '/manifest.json' ||
req.path === '/sw.js' ||
req.path === '/favicon.ico' ||
req.path.startsWith('/assets/')
req.path.startsWith('/assets/') ||
req.path.startsWith('/generated-media/')
) {
return next();
}
requireAuth(req, res, next);
});
// Interceptar requisições de mídia gerada para restaurar do banco se sumir do disco
app.get('/generated-media/:filename', async (req, res, next) => {
const { filename } = req.params;
const localPath = path.join(__dirname, 'public', 'generated-media', filename);
if (fs.existsSync(localPath)) {
return next();
}
try {
const dbRes = await dbPool.query(
`SELECT content_type, dados FROM escola.arquivos WHERE filename = $1`,
[filename]
);
if (dbRes.rowCount === 0) {
console.log(`[Media Server] Arquivo não encontrado no banco: ${filename}`);
return res.status(404).send('Arquivo não encontrado');
}
const { content_type, dados } = dbRes.rows[0];
const mediaDir = path.join(__dirname, 'public', 'generated-media');
if (!fs.existsSync(mediaDir)) {
fs.mkdirSync(mediaDir, { recursive: true });
}
fs.writeFileSync(localPath, dados);
console.log(`[Media Server] Arquivo ${filename} carregado do banco de dados e gravado em cache.`);
res.setHeader('Content-Type', content_type);
return res.send(dados);
} catch (err) {
console.error(`[Media Server] Erro ao carregar ${filename} do banco:`, err);
return res.status(500).send('Erro ao carregar arquivo');
}
});
// Servir arquivos estáticos do diretório public
app.use(express.static(path.join(__dirname, 'public')));
@@ -3897,8 +3984,9 @@ app.post('/api/chat', requireAuth, async (req, res) => {
const fileName = `imagem_${Date.now()}.jpg`;
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, imgBuffer);
await backupMediaFile(filePath, imgBuffer);
const localUrl = `/generated-media/${fileName}`;
console.log(`[Smart Router] Imagem salva em: ${localUrl}`);
console.log(`[Smart Router] Imagem salva em e backup no banco: ${localUrl}`);
res.write(`data: ${JSON.stringify({ type: 'image', url: localUrl, originalPrompt: promptText })}\n\n`);
res.write('data: [DONE]\n\n');
@@ -3996,9 +4084,11 @@ app.post('/api/chat', requireAuth, async (req, res) => {
}
const fileName = `musica_${Date.now()}.mp3`;
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, Buffer.from(audioHex, 'hex'));
const audioBuffer = Buffer.from(audioHex, 'hex');
fs.writeFileSync(filePath, audioBuffer);
await backupMediaFile(filePath, audioBuffer);
const audioUrl = `/generated-media/${fileName}`;
console.log(`[Smart Router] Música salva em: ${audioUrl}`);
console.log(`[Smart Router] Música salva em e backup no banco: ${audioUrl}`);
res.write(`data: ${JSON.stringify({ type: 'audio', url: audioUrl, originalPrompt: promptText })}\n\n`);
res.write('data: [DONE]\n\n');
@@ -4108,10 +4198,12 @@ app.post('/api/chat', requireAuth, async (req, res) => {
const fileName = `video_${Date.now()}.mp4`;
const filePath = path.join(mediaDir, fileName);
fs.writeFileSync(filePath, Buffer.from(videoBuffer));
const vBuf = Buffer.from(videoBuffer);
fs.writeFileSync(filePath, vBuf);
await backupMediaFile(filePath, vBuf);
const localVideoUrl = `/generated-media/${fileName}`;
console.log(`[Smart Router] Vídeo salvo localmente em: ${localVideoUrl}`);
console.log(`[Smart Router] Vídeo salvo em e backup no banco: ${localVideoUrl}`);
res.write(`data: ${JSON.stringify({ type: 'video', url: localVideoUrl, originalPrompt: promptText })}\n\n`);
res.write('data: [DONE]\n\n');