feat(media): registrar imagenes en biblioteca de medios de Appwrite e insertar como imagen destacada con resolucion 640x360 y 640x640
This commit is contained in:
parent
0e9bb2365e
commit
c6b825931f
@ -2,7 +2,7 @@ import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
|
||||
import { createAdminClient } from '@/lib/appwrite.server';
|
||||
import { Query } from 'node-appwrite';
|
||||
import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
|
||||
import { Calendar, User, ArrowLeft, Newspaper } from 'lucide-react';
|
||||
import { Calendar, User, ArrowLeft, Newspaper, Tag } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { decodeHtmlEntities } from '@/lib/utils';
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
@ -222,13 +222,16 @@ export default async function NoticiaDetail({ params }: { params: Promise<{ slug
|
||||
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
|
||||
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
|
||||
let imagenUrl = post.og_image_url || null;
|
||||
const isSquare = post.tipo === 'evento' || post.tipo === 'aviso' ||
|
||||
(typeof post.tipo_publicacion_id === 'object' && (post.tipo_publicacion_id.codigo === 'EVENTO' || post.tipo_publicacion_id.codigo === 'CLASIFICADO'));
|
||||
const imgHeight = isSquare ? 640 : 360;
|
||||
|
||||
if (post.imagen_destacada_id) {
|
||||
try {
|
||||
const client = await createAdminClient();
|
||||
const mediaRes = await client.databases.getDocument(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', post.imagen_destacada_id);
|
||||
if (mediaRes) {
|
||||
imagenUrl = `${ENDPOINT}/storage/buckets/${mediaRes.bucket_id}/files/${mediaRes.file_id}/preview?project=${PROJECT_ID}&width=640&height=360&quality=90`;
|
||||
imagenUrl = `${ENDPOINT}/storage/buckets/${mediaRes.bucket_id}/files/${mediaRes.file_id}/preview?project=${PROJECT_ID}&width=640&height=${imgHeight}&quality=90`;
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
@ -248,21 +251,22 @@ export default async function NoticiaDetail({ params }: { params: Promise<{ slug
|
||||
<article className="flex-1 max-w-4xl overflow-hidden">
|
||||
<Link href="/" className="inline-flex items-center gap-2 text-cyan-400 font-bold hover:text-cyan-300 transition-colors mb-8 group">
|
||||
<ArrowLeft size={18} className="transition-transform group-hover:-translate-x-1" />
|
||||
VOLVER A NOTICIAS
|
||||
<span>Volver a la portada</span>
|
||||
</Link>
|
||||
|
||||
<header className="mb-10">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<span className="px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-500/30 text-cyan-400 text-xs font-black tracking-widest uppercase">
|
||||
{post.tipo?.toUpperCase() || 'NOTICIA'}
|
||||
<header className="mb-10 space-y-6">
|
||||
<div className="flex flex-wrap items-center gap-4 text-xs font-bold uppercase tracking-wider text-slate-400">
|
||||
<span className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-cyan-500/10 text-cyan-400 border border-cyan-500/20">
|
||||
<Tag size={14} />
|
||||
{typeof post.tipo_publicacion_id === 'object' ? post.tipo_publicacion_id.descripcion : (post.tipo || 'Noticia')}
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-slate-800/60 border border-white/5">
|
||||
<Calendar size={14} />
|
||||
{fechaFormateada}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 text-slate-500 text-sm font-medium">
|
||||
<Calendar size={16} />
|
||||
<span>{fechaFormateada}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-6xl font-outfit font-black text-white leading-tight mb-6">
|
||||
<h1 className="text-3xl md:text-5xl font-black font-outfit text-white tracking-tight leading-tight">
|
||||
{decodeHtmlEntities(post.titulo)}
|
||||
</h1>
|
||||
|
||||
@ -283,12 +287,12 @@ export default async function NoticiaDetail({ params }: { params: Promise<{ slug
|
||||
}} />
|
||||
</div>
|
||||
) : imagenUrl ? (
|
||||
<div className="relative aspect-video w-full rounded-3xl overflow-hidden mb-12 border border-white/10 shadow-2xl bg-black">
|
||||
<div className={`relative ${isSquare ? 'aspect-square max-w-xl mx-auto' : 'aspect-video'} w-full rounded-3xl overflow-hidden mb-12 border border-white/10 shadow-2xl bg-black`}>
|
||||
<img src={imagenUrl} alt={post.titulo} className="w-full h-full object-cover" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-slate-950/50 to-transparent"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative aspect-video w-full rounded-3xl overflow-hidden mb-12 border border-white/10 shadow-2xl bg-gradient-to-br from-slate-900 to-slate-950 flex flex-col items-center justify-center gap-4">
|
||||
<div className={`relative ${isSquare ? 'aspect-square max-w-xl mx-auto' : 'aspect-video'} w-full rounded-3xl overflow-hidden mb-12 border border-white/10 shadow-2xl bg-gradient-to-br from-slate-900 to-slate-950 flex flex-col items-center justify-center gap-4`}>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--accent-glow))] opacity-5 pointer-events-none"></div>
|
||||
<Newspaper size={48} className="text-cyan-500/40 animate-pulse" />
|
||||
<span className="text-slate-500 font-bold uppercase tracking-widest text-xs font-outfit">Almeira Web</span>
|
||||
|
||||
@ -110,26 +110,33 @@ export async function POST(req: Request) {
|
||||
|
||||
// LOGIC 2: PROCESAR NUEVO MENSAJE PARA GEMINI
|
||||
// Evitámos tratar de procesar respuestas cortas o mensajes vacíos
|
||||
if (!text && !messageData.imageMessage) {
|
||||
if (!text && !messageData.imageMessage && !messageData.videoMessage && !messageData.documentMessage) {
|
||||
return NextResponse.json({ success: true, message: 'Ignored empty message' }, { status: 200 });
|
||||
}
|
||||
|
||||
// Verificamos si tiene multimedia
|
||||
// Verificamos si tiene multimedia directa o en mensaje reciente
|
||||
let base64Media = undefined;
|
||||
let mimeType = undefined;
|
||||
let imageId = undefined;
|
||||
let storageFileId = undefined;
|
||||
let featuredMediaDocId = undefined;
|
||||
|
||||
if (messageData.imageMessage || messageData.videoMessage || messageData.documentMessage) {
|
||||
const media = await getBase64FromMediaMessage(data);
|
||||
if (media) {
|
||||
base64Media = media.base64;
|
||||
mimeType = media.mimetype;
|
||||
}
|
||||
}
|
||||
|
||||
// Subimos a Appwrite Storage
|
||||
// Llamar a Gemini para estructurar la publicación
|
||||
const parsed = await processWhatsAppMessage(text, base64Media, mimeType);
|
||||
|
||||
// Si tenemos multimedia, subimos a Storage y creamos el registro en la Biblioteca de Medios (Colección 'media')
|
||||
if (base64Media && mimeType) {
|
||||
try {
|
||||
const buffer = Buffer.from(base64Media, 'base64');
|
||||
// Appwrite requiere un InputFile o similar. node-appwrite maneja InputFile.fromBuffer
|
||||
const extension = mimeType.split('/')[1] || 'jpg';
|
||||
const fileName = `wa_${Date.now()}.${extension}`;
|
||||
const fileName = `wa_${parsed.tipo}_${Date.now()}.${extension}`;
|
||||
const file = InputFile.fromBuffer(buffer, fileName);
|
||||
|
||||
const uploadedFile = await storage.createFile(
|
||||
@ -137,12 +144,38 @@ export async function POST(req: Request) {
|
||||
ID.unique(),
|
||||
file
|
||||
);
|
||||
imageId = uploadedFile.$id;
|
||||
storageFileId = uploadedFile.$id;
|
||||
|
||||
// Crear registro en la Biblioteca de Medios (Colección 'media')
|
||||
const mediaDoc = await databases.createDocument(
|
||||
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
||||
'media',
|
||||
ID.unique(),
|
||||
{
|
||||
file_id: storageFileId,
|
||||
bucket_id: APPWRITE_ALMEIRA_CONFIG.mediaBucketId,
|
||||
titulo: truncateString(parsed.titulo, 240),
|
||||
alt_text: truncateString(parsed.titulo, 240),
|
||||
descripcion_seo: truncateString(parsed.seo_descripcion, 450),
|
||||
tipo: mimeType.startsWith('video') ? 'video' : 'imagen'
|
||||
}
|
||||
);
|
||||
featuredMediaDocId = mediaDoc.$id;
|
||||
console.log(`[WhatsApp Webhook] Imagen subida a Biblioteca de Medios (ID: ${featuredMediaDocId}, Storage: ${storageFileId})`);
|
||||
} catch (err: any) {
|
||||
console.error('[WhatsApp Webhook] Error subiendo multimedia a Appwrite:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Llamar a Gemini
|
||||
const parsed = await processWhatsAppMessage(text, base64Media, mimeType);
|
||||
// Determinar dimensiones según el tipo de publicación
|
||||
// Noticias: 640x360 (16:9) | Clasificados y Eventos: 640x640 (1:1)
|
||||
const isSquare = parsed.tipo === 'evento' || parsed.tipo === 'aviso';
|
||||
const imgHeight = isSquare ? 640 : 360;
|
||||
let ogImageUrl = undefined;
|
||||
|
||||
if (storageFileId) {
|
||||
ogImageUrl = `${APPWRITE_ALMEIRA_CONFIG.endpoint}/storage/buckets/${APPWRITE_ALMEIRA_CONFIG.mediaBucketId}/files/${storageFileId}/preview?project=${APPWRITE_ALMEIRA_CONFIG.projectId}&width=640&height=${imgHeight}&quality=85`;
|
||||
}
|
||||
|
||||
// Determinar fecha de vencimiento
|
||||
let fechaVencimiento = parsed.fecha_vencimiento;
|
||||
@ -150,22 +183,26 @@ export async function POST(req: Request) {
|
||||
fechaVencimiento = getLastDayOfMonth();
|
||||
}
|
||||
|
||||
// Buscar un tipo de publicación por defecto (Requerido por Appwrite Schema)
|
||||
// Buscar tipo de publicación correspondiente (NOTICIA, EVENTO, CLASIFICADO)
|
||||
let tipoPublicacionId = '';
|
||||
try {
|
||||
const types = await databases.listDocuments(
|
||||
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
||||
APPWRITE_ALMEIRA_CONFIG.collections.publicationTypes,
|
||||
[]
|
||||
[Query.limit(20)]
|
||||
);
|
||||
if (types.documents.length > 0) {
|
||||
tipoPublicacionId = types.documents[0].$id;
|
||||
const targetCode = parsed.tipo === 'evento' ? 'EVENTO' : parsed.tipo === 'aviso' ? 'CLASIFICADO' : 'NOTICIA';
|
||||
const matched = types.documents.find((d: any) => d.codigo === targetCode) ||
|
||||
types.documents.find((d: any) => d.codigo === 'NOTICIA') ||
|
||||
types.documents[0];
|
||||
if (matched) {
|
||||
tipoPublicacionId = matched.$id;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('No se pudo obtener un tipo de publicación por defecto:', e);
|
||||
console.warn('No se pudo obtener el tipo de publicación en Appwrite:', e);
|
||||
}
|
||||
|
||||
// Crear Post en Appwrite
|
||||
// Crear Post en Appwrite (Vinculando la imagen destacada de la biblioteca de medios)
|
||||
const newPost = await databases.createDocument(
|
||||
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
||||
APPWRITE_ALMEIRA_CONFIG.collections.posts,
|
||||
@ -178,14 +215,16 @@ export async function POST(req: Request) {
|
||||
seo_titulo: truncateString(parsed.titulo, 60),
|
||||
seo_descripcion: truncateString(parsed.seo_descripcion, 155),
|
||||
contenido: parsed.contenido,
|
||||
estado: 'borrador', // Se requiere aprobación
|
||||
estado: 'borrador', // Requiere aprobación
|
||||
fecha_publicacion: new Date().toISOString(),
|
||||
...(tipoPublicacionId && { tipo_publicacion_id: tipoPublicacionId }),
|
||||
...(fechaVencimiento && { fecha_finalizacion: new Date(fechaVencimiento).toISOString() }),
|
||||
...(imageId && { imagen_destacada_id: imageId })
|
||||
...(featuredMediaDocId && { imagen_destacada_id: featuredMediaDocId }),
|
||||
...(ogImageUrl && { og_image_url: ogImageUrl })
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Notificar al grupo de WhatsApp pidiendo aprobación
|
||||
const requestText = `📰 *Publicación Procesada (Pendiente de Aprobación)*\n\n`
|
||||
+ `*Título:* ${parsed.titulo}\n`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user