Compare commits

..

No commits in common. "main" and "contenu" have entirely different histories.

8 changed files with 60 additions and 1339 deletions

View file

@ -1,32 +0,0 @@
name: Auto-Retry on Timeout
on:
workflow_run:
workflows: ["Deploy to Cloudflare Pages"]
types: [completed]
jobs:
retry-job:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled' }}
permissions:
actions: write
contents: read
steps:
- name: Relancer le workflow (Retry)
if: ${{ github.event.workflow_run.run_attempt < 3 }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Previous run failed. Attempting rerun #${{ github.event.workflow_run.run_attempt }}..."
# Use 'run rerun' to increment the attempt counter properly
# Add --repo to fix the "not a git repository" error
gh run rerun ${{ github.event.workflow_run.id }} \
--repo ${{ github.repository }} \
--failed
- name: Stop Retrying
if: ${{ github.event.workflow_run.run_attempt >= 3 }}
run: echo "Max attempts reached (3). Stopping to prevent loop."

View file

@ -11,7 +11,6 @@ on:
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60
permissions: permissions:
contents: read contents: read
deployments: write deployments: write

1256
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,6 @@
"dependencies": { "dependencies": {
"@docusaurus/core": "^3.9.2", "@docusaurus/core": "^3.9.2",
"@docusaurus/preset-classic": "^3.9.2", "@docusaurus/preset-classic": "^3.9.2",
"@docusaurus/theme-mermaid": "^3.9.2",
"@easyops-cn/docusaurus-search-local": "^0.52.1", "@easyops-cn/docusaurus-search-local": "^0.52.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"docusaurus-plugin-image-zoom": "^3.0.1", "docusaurus-plugin-image-zoom": "^3.0.1",

View file

@ -2,16 +2,8 @@ const Parser = require('rss-parser');
const { XMLParser } = require('fast-xml-parser'); const { XMLParser } = require('fast-xml-parser');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const https = require('https');
const PER_REQUEST_TIMEOUT_MS = 20_000; const http = require('http');
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 {
@ -21,15 +13,13 @@ module.exports = function (context, options) {
console.log('[RSS Aggregator] Récupération des flux RSS...'); console.log('[RSS Aggregator] Récupération des flux RSS...');
const parser = new Parser({ const parser = new Parser({
timeout: PER_REQUEST_TIMEOUT_MS, timeout: 10000,
requestOptions: {
agent: false
},
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');
@ -39,6 +29,7 @@ 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;
@ -59,26 +50,22 @@ 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...`);
let globalTimedOut = false; // Traitement par lots de 5 flux en parallèle pour ne pas surcharger
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 withTimeout(parser.parseURL(feedInfo.xmlUrl), PER_REQUEST_TIMEOUT_MS); const feed = await parser.parseURL(feedInfo.xmlUrl);
// 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;
@ -101,8 +88,7 @@ module.exports = function (context, options) {
allItems.push(...batchResults.flat()); allItems.push(...batchResults.flat());
} }
clearTimeout(globalTimeoutId); // Grouper par catégorie et trier
const groupedByCategory = new Map(); const groupedByCategory = new Map();
allItems.forEach((item) => { allItems.forEach((item) => {
@ -112,6 +98,7 @@ 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,
@ -133,9 +120,13 @@ 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);
}, },
}; };

View file

@ -37,7 +37,6 @@ 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,
@ -78,22 +77,9 @@ module.exports = function pluginUnifiedTags(context, options) {
}); });
} }
const sortedTagsData = new Map(); const allTags = Array.from(tagsMap.values()).sort((a, b) =>
tagsMap.forEach((tagData, tagKey) => { a.label.localeCompare(b.label)
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) => {
@ -104,19 +90,40 @@ module.exports = function pluginUnifiedTags(context, options) {
tagsByLetter[firstLetter].push(tag); tagsByLetter[firstLetter].push(tag);
}); });
setGlobalData({allTags, tagsByLetter}); const globalData = {
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}/`;
sortedTagsData.forEach((tagData, tagKey) => { Array.from(tagsMap.entries()).forEach(([tagKey, tagData]) => {
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,
}, },
}); });
}); });

View file

@ -26,6 +26,8 @@ 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>

View file

@ -1,6 +1,7 @@
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';
@ -26,12 +27,22 @@ interface TagData {
items: TagItem[]; items: TagItem[];
} }
interface TagPageProps { interface TagsMapData {
tagKey: string; [tagKey: string]: TagData;
tagData: TagData;
} }
export default function TagPage({tagKey, tagData}: TagPageProps): JSX.Element { interface UnifiedTagsData {
tagsMap: TagsMapData;
}
interface TagPageProps {
tagKey: string;
}
export default function TagPage(props: 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>