Fix cartazes deletion and media persistence
This commit is contained in:
+8
-8
@@ -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 => {
|
document.querySelectorAll('.btn-ver-estudio').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
const id = e.target.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/music/${id}`);
|
const res = await fetch(`/api/music/${id}`);
|
||||||
const itemData = await res.json();
|
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 => {
|
document.querySelectorAll('.btn-del-estudio').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
if(!confirm('Tem certeza que deseja apagar esta canção?')) return;
|
if(!confirm('Tem certeza que deseja apagar esta canção?')) return;
|
||||||
const id = e.target.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
try {
|
try {
|
||||||
await fetch(`/api/music/${id}`, { method: 'DELETE' });
|
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.'); }
|
} catch (err) { alert('Erro ao deletar.'); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -6800,7 +6800,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
document.querySelectorAll('.btn-ver-mindlab').forEach(btn => {
|
document.querySelectorAll('.btn-ver-mindlab').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
const id = e.target.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/mindlab/${id}`, {
|
const res = await fetch(`/api/mindlab/${id}`, {
|
||||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||||
@@ -6820,13 +6820,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.querySelectorAll('.btn-del-mindlab').forEach(btn => {
|
document.querySelectorAll('.btn-del-mindlab').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
if(!confirm('Tem certeza que deseja apagar este planejamento?')) return;
|
if(!confirm('Tem certeza que deseja apagar este planejamento?')) return;
|
||||||
const id = e.target.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
try {
|
try {
|
||||||
await fetch(`/api/mindlab/${id}`, {
|
await fetch(`/api/mindlab/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
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.'); }
|
} catch (err) { alert('Erro ao deletar.'); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -7188,7 +7188,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Bind Ver/Editar Buttons
|
// Bind Ver/Editar Buttons
|
||||||
document.querySelectorAll('.btn-ver-cartaz').forEach(btn => {
|
document.querySelectorAll('.btn-ver-cartaz').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
const id = e.target.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`/api/cartazes/${id}`, {
|
const r = await fetch(`/api/cartazes/${id}`, {
|
||||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||||
@@ -7223,7 +7223,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.querySelectorAll('.btn-del-cartaz').forEach(btn => {
|
document.querySelectorAll('.btn-del-cartaz').forEach(btn => {
|
||||||
btn.addEventListener('click', async (e) => {
|
btn.addEventListener('click', async (e) => {
|
||||||
if (!confirm('Tem certeza que deseja apagar este cartaz?')) return;
|
if (!confirm('Tem certeza que deseja apagar este cartaz?')) return;
|
||||||
const id = e.target.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`/api/cartazes/${id}`, {
|
const r = await fetch(`/api/cartazes/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
|||||||
@@ -82,6 +82,30 @@ const dbPool = new Pool({
|
|||||||
connectionString: process.env.DATABASE_URL
|
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 CONFIG_FILE_PATH = path.join(__dirname, 'agent_config.json');
|
||||||
const KNOWLEDGE_FILE_PATH = path.join(__dirname, 'conhecimento_adquirido.json');
|
const KNOWLEDGE_FILE_PATH = path.join(__dirname, 'conhecimento_adquirido.json');
|
||||||
const TEMPERATURE_PRESETS = {
|
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 fileName = `estudio_musica_${Date.now()}.mp3`;
|
||||||
const filePath = path.join(mediaDir, fileName);
|
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}`;
|
const audioUrl = `/generated-media/${fileName}`;
|
||||||
|
|
||||||
// 6. Retornar dados para o frontend
|
// 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 fileName = `quadrinho_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
|
||||||
const filePath = path.join(mediaDir, fileName);
|
const filePath = path.join(mediaDir, fileName);
|
||||||
fs.writeFileSync(filePath, imgBuffer);
|
fs.writeFileSync(filePath, imgBuffer);
|
||||||
|
await backupMediaFile(filePath, imgBuffer);
|
||||||
|
|
||||||
res.json({ imageUrl: `/generated-media/${fileName}` });
|
res.json({ imageUrl: `/generated-media/${fileName}` });
|
||||||
} catch (error) {
|
} 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.' });
|
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}` });
|
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 fileName = `garatuja_${Date.now()}${ext}`;
|
||||||
const filePath = path.join(mediaDir, fileName);
|
const filePath = path.join(mediaDir, fileName);
|
||||||
fs.writeFileSync(filePath, file.buffer);
|
fs.writeFileSync(filePath, file.buffer);
|
||||||
|
await backupMediaFile(filePath, file.buffer);
|
||||||
const imageUrl = `/generated-media/${fileName}`;
|
const imageUrl = `/generated-media/${fileName}`;
|
||||||
|
|
||||||
const base64Image = file.buffer.toString('base64');
|
const base64Image = file.buffer.toString('base64');
|
||||||
@@ -1448,7 +1486,9 @@ REGRAS:
|
|||||||
if (imgFetch.ok) {
|
if (imgFetch.ok) {
|
||||||
const imgBuffer = Buffer.from(await imgFetch.arrayBuffer());
|
const imgBuffer = Buffer.from(await imgFetch.arrayBuffer());
|
||||||
const fileName = `cartaz_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
|
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}`;
|
generatedImageUrl = `/generated-media/${fileName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2063,6 +2103,15 @@ async function initDatabase() {
|
|||||||
imagem_url TEXT,
|
imagem_url TEXT,
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
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
|
// 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 === '/manifest.json' ||
|
||||||
req.path === '/sw.js' ||
|
req.path === '/sw.js' ||
|
||||||
req.path === '/favicon.ico' ||
|
req.path === '/favicon.ico' ||
|
||||||
req.path.startsWith('/assets/')
|
req.path.startsWith('/assets/') ||
|
||||||
|
req.path.startsWith('/generated-media/')
|
||||||
) {
|
) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
requireAuth(req, res, 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
|
// Servir arquivos estáticos do diretório public
|
||||||
app.use(express.static(path.join(__dirname, '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 fileName = `imagem_${Date.now()}.jpg`;
|
||||||
const filePath = path.join(mediaDir, fileName);
|
const filePath = path.join(mediaDir, fileName);
|
||||||
fs.writeFileSync(filePath, imgBuffer);
|
fs.writeFileSync(filePath, imgBuffer);
|
||||||
|
await backupMediaFile(filePath, imgBuffer);
|
||||||
const localUrl = `/generated-media/${fileName}`;
|
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: ${JSON.stringify({ type: 'image', url: localUrl, originalPrompt: promptText })}\n\n`);
|
||||||
res.write('data: [DONE]\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 fileName = `musica_${Date.now()}.mp3`;
|
||||||
const filePath = path.join(mediaDir, fileName);
|
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}`;
|
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: ${JSON.stringify({ type: 'audio', url: audioUrl, originalPrompt: promptText })}\n\n`);
|
||||||
res.write('data: [DONE]\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 fileName = `video_${Date.now()}.mp4`;
|
||||||
const filePath = path.join(mediaDir, fileName);
|
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}`;
|
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: ${JSON.stringify({ type: 'video', url: localVideoUrl, originalPrompt: promptText })}\n\n`);
|
||||||
res.write('data: [DONE]\n\n');
|
res.write('data: [DONE]\n\n');
|
||||||
|
|||||||
Reference in New Issue
Block a user