185 lines
5.4 KiB
JavaScript
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]);
|
|
}
|