1
Files
2025/tools/update_lemmy_post_dates.js

185 lines
5.4 KiB
JavaScript

#!/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]);
}