222 lines
10 KiB
TypeScript
222 lines
10 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { ImageResponse } from 'next/og';
|
|
import { Document, Paragraph, TextRun, Packer, ImageRun, HeadingLevel, ExternalHyperlink, AlignmentType } from 'docx';
|
|
import { createAdminClient } from '@/lib/appwrite.server';
|
|
import { Query } from 'node-appwrite';
|
|
import { APPWRITE_ALMEIRA_CONFIG, endpoint, projectId } from '@/lib/appwrite';
|
|
import { collectionMap } from '@/lib/schema';
|
|
|
|
function getAppwriteImageUrl(fileId: string, bucketId?: string, width = 400, height = 250, isVideo = false) {
|
|
if (!fileId) return '';
|
|
const finalBucketId = bucketId || APPWRITE_ALMEIRA_CONFIG.mediaBucketId;
|
|
if (isVideo) {
|
|
return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/view?project=${projectId}`;
|
|
}
|
|
return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/preview?project=${projectId}&width=${width}&height=${height}&quality=80`;
|
|
}
|
|
|
|
export const runtime = 'nodejs';
|
|
|
|
export async function GET(request: Request) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const q = searchParams.get('q') || '';
|
|
const sort = searchParams.get('sort') || 'fecha_publicacion';
|
|
const dir = searchParams.get('dir') || 'desc';
|
|
const status = searchParams.get('status') || '';
|
|
const year = searchParams.get('year');
|
|
const month = searchParams.get('month');
|
|
|
|
const client = await createAdminClient();
|
|
const postsCollectionId = collectionMap['posts'];
|
|
const mediaCollectionId = collectionMap['media'];
|
|
|
|
if (!postsCollectionId) {
|
|
return NextResponse.json({ error: 'Configuración de colección incorrecta' }, { status: 500 });
|
|
}
|
|
|
|
const queries = [Query.limit(100), Query.offset(0)];
|
|
|
|
if (dir === 'asc') queries.push(Query.orderAsc(sort));
|
|
else queries.push(Query.orderDesc(sort));
|
|
|
|
if (q) queries.push(Query.contains('titulo', q));
|
|
|
|
if (status && status !== 'all') {
|
|
if (status === 'publicado') queries.push(Query.equal('estado', ['publicado', 'Publicado']));
|
|
else if (status === 'borrador') queries.push(Query.equal('estado', ['borrador', 'Borrador']));
|
|
else if (status === 'pendiente') queries.push(Query.equal('estado', ['pendiente', 'Pendiente']));
|
|
else if (status === 'archivado') queries.push(Query.equal('estado', ['archivado', 'Archivado']));
|
|
else queries.push(Query.equal('estado', status));
|
|
}
|
|
|
|
if (year || month) {
|
|
const filterYear = year ? Number(year) : new Date().getFullYear();
|
|
if (month) {
|
|
const startDate = new Date(Date.UTC(filterYear, Number(month) - 1, 1)).toISOString();
|
|
const endDate = new Date(Date.UTC(filterYear, Number(month), 1)).toISOString();
|
|
queries.push(Query.greaterThanEqual('fecha_publicacion', startDate));
|
|
queries.push(Query.lessThan('fecha_publicacion', endDate));
|
|
} else {
|
|
const startDate = new Date(Date.UTC(filterYear, 0, 1)).toISOString();
|
|
const endDate = new Date(Date.UTC(filterYear + 1, 0, 1)).toISOString();
|
|
queries.push(Query.greaterThanEqual('fecha_publicacion', startDate));
|
|
queries.push(Query.lessThan('fecha_publicacion', endDate));
|
|
}
|
|
}
|
|
|
|
const response = await client.databases.listDocuments(
|
|
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
|
postsCollectionId,
|
|
queries
|
|
);
|
|
|
|
const posts = response.documents;
|
|
|
|
const mediaIds = Array.from(new Set(posts.map(p => typeof p.imagen_destacada_id === 'object' && p.imagen_destacada_id !== null ? p.imagen_destacada_id.$id : p.imagen_destacada_id).filter(id => typeof id === 'string' && id.length > 0) as string[]));
|
|
let mediaDocs: Record<string, any> = {};
|
|
|
|
if (mediaIds.length > 0 && mediaCollectionId) {
|
|
try {
|
|
const relRes = await client.databases.listDocuments(
|
|
APPWRITE_ALMEIRA_CONFIG.databaseId,
|
|
mediaCollectionId,
|
|
[Query.equal('$id', mediaIds), Query.limit(100)]
|
|
);
|
|
relRes.documents.forEach(doc => {
|
|
mediaDocs[doc.$id] = doc;
|
|
});
|
|
} catch (err) {
|
|
console.error('Error resolviendo las imágenes para el reporte', err);
|
|
}
|
|
}
|
|
|
|
const docChildren: any[] = [
|
|
new Paragraph({
|
|
text: `Reporte de Publicaciones - Almeira Web`,
|
|
heading: HeadingLevel.HEADING_1,
|
|
alignment: AlignmentType.CENTER,
|
|
spacing: { after: 400 }
|
|
})
|
|
];
|
|
|
|
for (const post of posts) {
|
|
const fechaFormat = post.fecha_publicacion ? new Date(post.fecha_publicacion).toLocaleDateString('es-CO') : 'Sin fecha';
|
|
const mediaId = typeof post.imagen_destacada_id === 'object' && post.imagen_destacada_id !== null ? post.imagen_destacada_id.$id : post.imagen_destacada_id;
|
|
|
|
let imageUrl = '';
|
|
if (mediaId && mediaDocs[mediaId]) {
|
|
const mediaInfo = mediaDocs[mediaId];
|
|
if (mediaInfo.tipo === 'imagen') {
|
|
imageUrl = getAppwriteImageUrl(mediaInfo.file_id, mediaInfo.bucket_id, 600, 400, false);
|
|
}
|
|
}
|
|
|
|
let imageAdded = false;
|
|
try {
|
|
const plainTextResumen = (post.resumen_redes || post.contenido || '').replace(/<[^>]*>?/gm, ' ').replace(/\s+/g, ' ').trim();
|
|
const safeResumen = plainTextResumen.length > 120 ? plainTextResumen.substring(0, 120) + '...' : plainTextResumen;
|
|
|
|
// Use native JSX now that the file is renamed to route.tsx
|
|
const cardImage = new ImageResponse(
|
|
(
|
|
<div style={{
|
|
display: 'flex', flexDirection: 'column', width: 600, height: 600,
|
|
backgroundColor: '#0f172a', borderRadius: 24,
|
|
overflow: 'hidden', border: '1px solid rgba(255,255,255,0.1)', fontFamily: 'sans-serif'
|
|
}}>
|
|
<div style={{ display: 'flex', width: 600, height: 337, backgroundColor: '#1e293b', position: 'relative' }}>
|
|
{imageUrl ? (
|
|
<img src={imageUrl} style={{ width: 600, height: 337, objectFit: 'cover' }} />
|
|
) : (
|
|
<div style={{ width: 600, height: 337, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<span style={{ color: '#475569', fontSize: 24, fontWeight: 900, textTransform: 'uppercase', letterSpacing: 4 }}>Almeira Web</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', padding: 24, flexGrow: 1 }}>
|
|
<h3 style={{ fontSize: 24, fontWeight: 'bold', margin: '0 0 12px 0', lineHeight: 1.3, color: 'white' }}>{post.titulo || 'Sin título'}</h3>
|
|
<p style={{ fontSize: 16, color: '#94a3b8', margin: 0, lineHeight: 1.6 }}>{safeResumen}</p>
|
|
</div>
|
|
</div>
|
|
),
|
|
{ width: 600, height: 600 }
|
|
);
|
|
|
|
const imgBuffer = await cardImage.arrayBuffer();
|
|
docChildren.push(
|
|
new Paragraph({
|
|
children: [
|
|
new ImageRun({
|
|
data: imgBuffer,
|
|
transformation: { width: 500, height: 333 },
|
|
type: 'png'
|
|
})
|
|
],
|
|
alignment: AlignmentType.CENTER,
|
|
spacing: { after: 200 }
|
|
})
|
|
);
|
|
imageAdded = true;
|
|
} catch (err) {
|
|
console.error('Error fetching image for Word Document', err);
|
|
}
|
|
|
|
if (!imageAdded) {
|
|
docChildren.push(
|
|
new Paragraph({
|
|
children: [
|
|
new TextRun({ text: "[No se pudo generar la tarjeta virtual]", italics: true })
|
|
],
|
|
alignment: AlignmentType.CENTER,
|
|
spacing: { after: 200 }
|
|
})
|
|
);
|
|
}
|
|
|
|
docChildren.push(
|
|
new Paragraph({
|
|
children: [
|
|
new ExternalHyperlink({
|
|
children: [
|
|
new TextRun({ text: `https://almeira.com.co/${post.slug || post.$id}`, style: "Hyperlink" }),
|
|
],
|
|
link: `https://almeira.com.co/${post.slug || post.$id}`,
|
|
})
|
|
],
|
|
spacing: { after: 400 }
|
|
})
|
|
);
|
|
|
|
docChildren.push(
|
|
new Paragraph({ text: "______________________________________________________", alignment: AlignmentType.CENTER, spacing: { after: 200 } })
|
|
);
|
|
}
|
|
|
|
if (posts.length === 0) {
|
|
docChildren.push(new Paragraph({ text: "No se encontraron publicaciones con los filtros seleccionados." }));
|
|
}
|
|
|
|
const doc = new Document({
|
|
sections: [{
|
|
properties: {},
|
|
children: docChildren,
|
|
}]
|
|
});
|
|
|
|
const buffer = await Packer.toBuffer(doc);
|
|
const dataArray = new Uint8Array(buffer);
|
|
|
|
return new NextResponse(dataArray, {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'Content-Disposition': `attachment; filename=reporte_publicaciones_${new Date().toISOString().split('T')[0]}.docx`,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
console.error('General error Word API:', error);
|
|
return NextResponse.json({ error: 'Error al generar el documento', details: error?.message || 'Error desconocido' }, { status: 500 });
|
|
}
|
|
}
|