Optimisation plugins RSS et tags
This commit is contained in:
parent
d0488c7a72
commit
f2f6e03777
4 changed files with 52 additions and 56 deletions
|
|
@ -5,6 +5,16 @@ const path = require('path');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|
||||||
|
const PER_REQUEST_TIMEOUT_MS = 20_000;
|
||||||
|
const GLOBAL_TIMEOUT_MS = 600_000;
|
||||||
|
|
||||||
|
function withTimeout(promise, ms) {
|
||||||
|
const timeout = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error(`Timeout après ${ms / 1000}s`)), ms)
|
||||||
|
);
|
||||||
|
return Promise.race([promise, timeout]);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function (context, options) {
|
module.exports = function (context, options) {
|
||||||
return {
|
return {
|
||||||
name: 'docusaurus-plugin-rss-aggregator',
|
name: 'docusaurus-plugin-rss-aggregator',
|
||||||
|
|
@ -12,14 +22,19 @@ module.exports = function (context, options) {
|
||||||
async loadContent() {
|
async loadContent() {
|
||||||
console.log('[RSS Aggregator] Récupération des flux RSS...');
|
console.log('[RSS Aggregator] Récupération des flux RSS...');
|
||||||
|
|
||||||
|
const httpAgent = new http.Agent({ keepAlive: false });
|
||||||
|
const httpsAgent = new https.Agent({ keepAlive: false });
|
||||||
|
|
||||||
const parser = new Parser({
|
const parser = new Parser({
|
||||||
timeout: 10000,
|
timeout: PER_REQUEST_TIMEOUT_MS,
|
||||||
|
requestOptions: {
|
||||||
|
agent: { http: httpAgent, https: httpsAgent }
|
||||||
|
},
|
||||||
customFields: {
|
customFields: {
|
||||||
item: ['description', 'content:encoded']
|
item: ['description', 'content:encoded']
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Lecture du fichier OPML
|
|
||||||
const opmlPath = path.join(context.siteDir, 'static', 'veille-tech.opml');
|
const opmlPath = path.join(context.siteDir, 'static', 'veille-tech.opml');
|
||||||
const opmlText = fs.readFileSync(opmlPath, 'utf-8');
|
const opmlText = fs.readFileSync(opmlPath, 'utf-8');
|
||||||
|
|
||||||
|
|
@ -29,7 +44,6 @@ module.exports = function (context, options) {
|
||||||
});
|
});
|
||||||
const opmlData = xmlParser.parse(opmlText);
|
const opmlData = xmlParser.parse(opmlText);
|
||||||
|
|
||||||
// Extraction des flux depuis le fichier OPML
|
|
||||||
const opmlFeeds = [];
|
const opmlFeeds = [];
|
||||||
const outlines = opmlData.opml.body.outline;
|
const outlines = opmlData.opml.body.outline;
|
||||||
|
|
||||||
|
|
@ -50,22 +64,26 @@ module.exports = function (context, options) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Récupération des flux RSS (articles des dernières 24h)
|
|
||||||
const allItems = [];
|
const allItems = [];
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const twentyFourHoursAgo = now - (24 * 60 * 60 * 1000);
|
const twentyFourHoursAgo = now - (24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
console.log(`[RSS Aggregator] Récupération de ${opmlFeeds.length} flux RSS...`);
|
console.log(`[RSS Aggregator] Récupération de ${opmlFeeds.length} flux RSS...`);
|
||||||
|
|
||||||
// Traitement par lots de 5 flux en parallèle pour ne pas surcharger
|
let globalTimedOut = false;
|
||||||
|
const globalTimeoutId = setTimeout(() => {
|
||||||
|
globalTimedOut = true;
|
||||||
|
console.warn('[RSS Aggregator] Timeout global atteint (600s), arrêt du traitement.');
|
||||||
|
}, GLOBAL_TIMEOUT_MS);
|
||||||
|
|
||||||
const batchSize = 5;
|
const batchSize = 5;
|
||||||
for (let i = 0; i < opmlFeeds.length; i += batchSize) {
|
for (let i = 0; i < opmlFeeds.length; i += batchSize) {
|
||||||
|
if (globalTimedOut) break;
|
||||||
const batch = opmlFeeds.slice(i, i + batchSize);
|
const batch = opmlFeeds.slice(i, i + batchSize);
|
||||||
const batchPromises = batch.map(async (feedInfo) => {
|
const batchPromises = batch.map(async (feedInfo) => {
|
||||||
try {
|
try {
|
||||||
const feed = await parser.parseURL(feedInfo.xmlUrl);
|
const feed = await withTimeout(parser.parseURL(feedInfo.xmlUrl), PER_REQUEST_TIMEOUT_MS);
|
||||||
|
|
||||||
// Filtrer les articles des dernières 24h
|
|
||||||
const recentItems = feed.items.filter((item) => {
|
const recentItems = feed.items.filter((item) => {
|
||||||
const itemDate = new Date(item.pubDate || item.isoDate || '');
|
const itemDate = new Date(item.pubDate || item.isoDate || '');
|
||||||
return itemDate.getTime() >= twentyFourHoursAgo;
|
return itemDate.getTime() >= twentyFourHoursAgo;
|
||||||
|
|
@ -88,7 +106,10 @@ module.exports = function (context, options) {
|
||||||
allItems.push(...batchResults.flat());
|
allItems.push(...batchResults.flat());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grouper par catégorie et trier
|
clearTimeout(globalTimeoutId);
|
||||||
|
httpAgent.destroy();
|
||||||
|
httpsAgent.destroy();
|
||||||
|
|
||||||
const groupedByCategory = new Map();
|
const groupedByCategory = new Map();
|
||||||
|
|
||||||
allItems.forEach((item) => {
|
allItems.forEach((item) => {
|
||||||
|
|
@ -98,7 +119,6 @@ module.exports = function (context, options) {
|
||||||
groupedByCategory.get(item.category).push(item);
|
groupedByCategory.get(item.category).push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trier les articles de chaque catégorie par date (plus récent en premier)
|
|
||||||
const groups = Array.from(groupedByCategory.entries())
|
const groups = Array.from(groupedByCategory.entries())
|
||||||
.map(([category, items]) => ({
|
.map(([category, items]) => ({
|
||||||
category,
|
category,
|
||||||
|
|
@ -120,13 +140,9 @@ module.exports = function (context, options) {
|
||||||
async contentLoaded({ content, actions }) {
|
async contentLoaded({ content, actions }) {
|
||||||
const { setGlobalData } = actions;
|
const { setGlobalData } = actions;
|
||||||
|
|
||||||
// Écrire les données dans un fichier JSON statique
|
|
||||||
const outputPath = path.join(context.siteDir, 'static', 'rss-feed-cache.json');
|
const outputPath = path.join(context.siteDir, 'static', 'rss-feed-cache.json');
|
||||||
fs.writeFileSync(outputPath, JSON.stringify(content, null, 2));
|
fs.writeFileSync(outputPath, JSON.stringify(content, null, 2));
|
||||||
|
|
||||||
console.log(`[RSS Aggregator] Données écrites dans ${outputPath}`);
|
console.log(`[RSS Aggregator] Données écrites dans ${outputPath}`);
|
||||||
|
|
||||||
// Rendre les données disponibles globalement
|
|
||||||
setGlobalData(content);
|
setGlobalData(content);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ module.exports = function pluginUnifiedTags(context, options) {
|
||||||
title: post.metadata.title,
|
title: post.metadata.title,
|
||||||
permalink: post.metadata.permalink,
|
permalink: post.metadata.permalink,
|
||||||
date: post.metadata.date,
|
date: post.metadata.date,
|
||||||
|
_ts: new Date(post.metadata.date).getTime(),
|
||||||
formattedDate: post.metadata.formattedDate,
|
formattedDate: post.metadata.formattedDate,
|
||||||
description: post.metadata.description,
|
description: post.metadata.description,
|
||||||
authors: post.metadata.authors,
|
authors: post.metadata.authors,
|
||||||
|
|
@ -77,9 +78,22 @@ module.exports = function pluginUnifiedTags(context, options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const allTags = Array.from(tagsMap.values()).sort((a, b) =>
|
const sortedTagsData = new Map();
|
||||||
a.label.localeCompare(b.label)
|
tagsMap.forEach((tagData, tagKey) => {
|
||||||
);
|
const sortedItems = tagData.items
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.type === 'blog' && b.type === 'blog') return b._ts - a._ts;
|
||||||
|
if (a.type === 'doc' && b.type === 'doc') return a.title.localeCompare(b.title);
|
||||||
|
return a.type === 'blog' ? -1 : 1;
|
||||||
|
})
|
||||||
|
.map(({_ts, ...item}) => item);
|
||||||
|
|
||||||
|
sortedTagsData.set(tagKey, {...tagData, items: sortedItems});
|
||||||
|
});
|
||||||
|
|
||||||
|
const allTags = Array.from(tagsMap.values())
|
||||||
|
.map(({items, ...tag}) => tag)
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
const tagsByLetter = {};
|
const tagsByLetter = {};
|
||||||
allTags.forEach((tag) => {
|
allTags.forEach((tag) => {
|
||||||
|
|
@ -90,40 +104,19 @@ module.exports = function pluginUnifiedTags(context, options) {
|
||||||
tagsByLetter[firstLetter].push(tag);
|
tagsByLetter[firstLetter].push(tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
const globalData = {
|
setGlobalData({allTags, tagsByLetter});
|
||||||
allTags,
|
|
||||||
tagsByLetter,
|
|
||||||
tagsMap: Object.fromEntries(
|
|
||||||
Array.from(tagsMap.entries()).map(([key, value]) => [
|
|
||||||
key,
|
|
||||||
{
|
|
||||||
...value,
|
|
||||||
items: value.items.sort((a, b) => {
|
|
||||||
if (a.type === 'blog' && b.type === 'blog') {
|
|
||||||
return new Date(b.date) - new Date(a.date);
|
|
||||||
}
|
|
||||||
if (a.type === 'doc' && b.type === 'doc') {
|
|
||||||
return a.title.localeCompare(b.title);
|
|
||||||
}
|
|
||||||
return a.type === 'blog' ? -1 : 1;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
])
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
setGlobalData(globalData);
|
|
||||||
|
|
||||||
const locale = context.i18n.currentLocale;
|
const locale = context.i18n.currentLocale;
|
||||||
const baseUrl = locale === context.i18n.defaultLocale ? '/' : `/${locale}/`;
|
const baseUrl = locale === context.i18n.defaultLocale ? '/' : `/${locale}/`;
|
||||||
|
|
||||||
Array.from(tagsMap.entries()).forEach(([tagKey, tagData]) => {
|
sortedTagsData.forEach((tagData, tagKey) => {
|
||||||
addRoute({
|
addRoute({
|
||||||
path: `${baseUrl}tags/${tagKey}`,
|
path: `${baseUrl}tags/${tagKey}`,
|
||||||
component: '@site/src/theme/TagPage',
|
component: '@site/src/theme/TagPage',
|
||||||
exact: true,
|
exact: true,
|
||||||
props: {
|
props: {
|
||||||
tagKey: tagKey,
|
tagKey,
|
||||||
|
tagData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ interface UnifiedTagsData {
|
||||||
export default function TagsPage(): JSX.Element {
|
export default function TagsPage(): JSX.Element {
|
||||||
const pluginData = usePluginData('docusaurus-plugin-unified-tags') as UnifiedTagsData;
|
const pluginData = usePluginData('docusaurus-plugin-unified-tags') as UnifiedTagsData;
|
||||||
|
|
||||||
console.log('Plugin data:', pluginData);
|
|
||||||
|
|
||||||
if (!pluginData || !pluginData.tagsByLetter) {
|
if (!pluginData || !pluginData.tagsByLetter) {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import {usePluginData} from '@docusaurus/useGlobalData';
|
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
import Translate, {translate} from '@docusaurus/Translate';
|
||||||
import Heading from '@theme/Heading';
|
import Heading from '@theme/Heading';
|
||||||
|
|
@ -27,22 +26,12 @@ interface TagData {
|
||||||
items: TagItem[];
|
items: TagItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagsMapData {
|
|
||||||
[tagKey: string]: TagData;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UnifiedTagsData {
|
|
||||||
tagsMap: TagsMapData;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TagPageProps {
|
interface TagPageProps {
|
||||||
tagKey: string;
|
tagKey: string;
|
||||||
|
tagData: TagData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TagPage(props: TagPageProps): JSX.Element {
|
export default function TagPage({tagKey, tagData}: TagPageProps): JSX.Element {
|
||||||
const {tagsMap} = usePluginData('docusaurus-plugin-unified-tags') as UnifiedTagsData;
|
|
||||||
const tagData = tagsMap[props.tagKey];
|
|
||||||
|
|
||||||
if (!tagData) {
|
if (!tagData) {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue