Synchronisation du contenu avec lemmy
This commit is contained in:
184
tools/update_lemmy_post_dates.js
Normal file
184
tools/update_lemmy_post_dates.js
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Met à jour la date de publication des posts Lemmy à partir des articles Hugo.
|
||||
* Pré-requis : accès en écriture à la base Postgres de Lemmy
|
||||
* (par exemple via LEMMY_DATABASE_URL=postgres:///lemmy?host=/run/postgresql&user=lemmy
|
||||
* et exécution en tant qu'utilisateur système lemmy).
|
||||
*
|
||||
* Règles :
|
||||
* - L'article doit contenir un frontmatter valide avec un champ date.
|
||||
* - L'article doit contenir un comments_url pointant vers /post/{id}.
|
||||
* - La date est appliquée sur post.published (timestamp avec fuseau Hugo).
|
||||
*/
|
||||
|
||||
const path = require("node:path");
|
||||
const { Pool } = require("pg");
|
||||
const { collectBundles } = require("./lib/content");
|
||||
const { parseFrontmatterDate } = require("./lib/datetime");
|
||||
const { readFrontmatterFile } = require("./lib/frontmatter");
|
||||
const { loadEnv } = require("./lib/env");
|
||||
|
||||
const CONTENT_ROOT = path.join(__dirname, "..", "content");
|
||||
const DEFAULT_DATABASE_URL = "postgres:///lemmy?host=/run/postgresql&user=lemmy";
|
||||
|
||||
main().then(
|
||||
() => {
|
||||
process.exit(0);
|
||||
},
|
||||
(error) => {
|
||||
console.error(`❌ Mise à jour interrompue : ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Point d'entrée : collecte les articles, se connecte à Postgres, applique les dates.
|
||||
*/
|
||||
async function main() {
|
||||
loadEnv();
|
||||
const databaseUrl = resolveDatabaseUrl();
|
||||
const pool = new Pool({ connectionString: databaseUrl });
|
||||
const bundles = await collectBundles(CONTENT_ROOT);
|
||||
const articles = collectArticlesWithPostId(bundles);
|
||||
|
||||
if (articles.length === 0) {
|
||||
console.log("Aucun article muni d'un comments_url et d'une date valide.");
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
let updated = 0;
|
||||
let unchanged = 0;
|
||||
let missing = 0;
|
||||
|
||||
for (const article of articles) {
|
||||
const targetDate = article.publication.set({ millisecond: 0 });
|
||||
const iso = targetDate.toISO();
|
||||
const row = await fetchPost(pool, article.postId);
|
||||
if (!row) {
|
||||
missing += 1;
|
||||
console.warn(`⚠️ Post ${article.postId} introuvable pour ${article.bundle.relativePath}`);
|
||||
continue;
|
||||
}
|
||||
const currentIso = new Date(row.published).toISOString();
|
||||
if (currentIso === targetDate.toUTC().toISO()) {
|
||||
unchanged += 1;
|
||||
continue;
|
||||
}
|
||||
await applyDate(pool, article.postId, iso);
|
||||
updated += 1;
|
||||
console.log(`✅ Post ${article.postId} mis à ${iso} (${article.bundle.relativePath})`);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
console.log("");
|
||||
console.log("Résumé des ajustements Lemmy");
|
||||
console.log(`Posts mis à jour : ${updated}`);
|
||||
console.log(`Posts déjà alignés : ${unchanged}`);
|
||||
console.log(`Posts introuvables : ${missing}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine l'URL de connexion Postgres.
|
||||
* @returns {string} Chaîne de connexion.
|
||||
*/
|
||||
function resolveDatabaseUrl() {
|
||||
if (typeof process.env.LEMMY_DATABASE_URL === "string" && process.env.LEMMY_DATABASE_URL.trim()) {
|
||||
return process.env.LEMMY_DATABASE_URL.trim();
|
||||
}
|
||||
if (typeof process.env.DATABASE_URL === "string" && process.env.DATABASE_URL.trim()) {
|
||||
return process.env.DATABASE_URL.trim();
|
||||
}
|
||||
return DEFAULT_DATABASE_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit la liste des articles éligibles avec identifiant de post Lemmy.
|
||||
* @param {Array<object>} bundles Bundles Hugo.
|
||||
* @returns {Array<object>} Articles prêts à être appliqués.
|
||||
*/
|
||||
function collectArticlesWithPostId(bundles) {
|
||||
const articles = [];
|
||||
for (const bundle of bundles) {
|
||||
const frontmatter = readFrontmatterFile(bundle.indexPath);
|
||||
if (!frontmatter) {
|
||||
continue;
|
||||
}
|
||||
const publication = parseFrontmatterDate(frontmatter.data?.date);
|
||||
if (!publication) {
|
||||
continue;
|
||||
}
|
||||
const title = typeof frontmatter.data?.title === "string" ? frontmatter.data.title.trim() : "";
|
||||
if (!title) {
|
||||
continue;
|
||||
}
|
||||
const commentsUrl =
|
||||
typeof frontmatter.data?.comments_url === "string" ? frontmatter.data.comments_url.trim() : "";
|
||||
if (!commentsUrl) {
|
||||
continue;
|
||||
}
|
||||
const postId = extractPostId(commentsUrl);
|
||||
if (postId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
articles.push({
|
||||
bundle,
|
||||
publication,
|
||||
postId,
|
||||
});
|
||||
}
|
||||
|
||||
articles.sort((a, b) => {
|
||||
const diff = a.publication.toMillis() - b.publication.toMillis();
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
return a.bundle.relativePath.localeCompare(b.bundle.relativePath);
|
||||
});
|
||||
|
||||
return articles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait l'identifiant numérique d'un comments_url Lemmy.
|
||||
* @param {string} url URL issue du frontmatter.
|
||||
* @returns {number|null} Identifiant ou null si non reconnu.
|
||||
*/
|
||||
function extractPostId(url) {
|
||||
const trimmed = url.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const normalized = trimmed.replace(/\/+$/, "");
|
||||
const match = normalized.match(/\/(?:post|c\/[^/]+\/post)\/(\d+)(?:$|\?)/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return Number.parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un post Lemmy par identifiant.
|
||||
* @param {Pool} pool Pool Postgres.
|
||||
* @param {number} postId Identifiant du post.
|
||||
* @returns {Promise<object|null>} Enregistrement ou null.
|
||||
*/
|
||||
async function fetchPost(pool, postId) {
|
||||
const result = await pool.query("select id, published from post where id = $1", [postId]);
|
||||
if (result.rowCount !== 1) {
|
||||
return null;
|
||||
}
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique la date ISO fournie sur le post ciblé.
|
||||
* @param {Pool} pool Pool Postgres.
|
||||
* @param {number} postId Identifiant du post.
|
||||
* @param {string} isoDate Timestamp ISO.
|
||||
*/
|
||||
async function applyDate(pool, postId, isoDate) {
|
||||
await pool.query("update post set published = $1 where id = $2", [isoDate, postId]);
|
||||
}
|
||||
Reference in New Issue
Block a user