All files / utils news-metadata.ts

100% Statements 21/21
90.9% Branches 10/11
100% Functions 5/5
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102                                    1x                 5x 5x   5x 5x 5x 5x                     5x   5x                               3x 3x 1x   3x                       3x 1x     2x 2x                           1x 1x 1x    
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module Utils/NewsMetadata
 * @description News metadata database management.
 * Maintains a JSON database of article metadata that can be loaded
 * by client-side JavaScript, removing the need to edit all index HTML
 * files when adding new articles.
 */
 
import fs from 'fs';
import path from 'path';
import { NEWS_DIR } from '../constants/config.js';
import { getNewsArticles, parseArticleFilename, formatSlug } from './file-utils.js';
import type { ArticleMetadataEntry, NewsMetadataDatabase } from '../types/index.js';
 
/** Default path for the metadata database file */
const METADATA_DB_PATH = path.join(NEWS_DIR, 'articles-metadata.json');
 
/**
 * Build metadata database from news article files
 *
 * @param newsDir - News directory path
 * @returns News metadata database object
 */
export function buildMetadataDatabase(newsDir: string = NEWS_DIR): NewsMetadataDatabase {
  const articleFiles = getNewsArticles(newsDir);
  const articles: ArticleMetadataEntry[] = [];
 
  for (const filename of articleFiles) {
    const parsed = parseArticleFilename(filename);
    Eif (parsed) {
      articles.push({
        filename: parsed.filename,
        date: parsed.date,
        slug: parsed.slug,
        lang: parsed.lang,
        title: formatSlug(parsed.slug),
      });
    }
  }
 
  // Sort by date (newest first)
  articles.sort((a, b) => b.date.localeCompare(a.date));
 
  return {
    lastUpdated: new Date().toISOString(),
    articles,
  };
}
 
/**
 * Write metadata database to JSON file
 *
 * @param database - Metadata database to write
 * @param outputPath - Output file path (defaults to news/articles-metadata.json)
 */
export function writeMetadataDatabase(
  database: NewsMetadataDatabase,
  outputPath: string = METADATA_DB_PATH
): void {
  const dir = path.dirname(outputPath);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  fs.writeFileSync(outputPath, JSON.stringify(database, null, 2), 'utf-8');
}
 
/**
 * Read metadata database from JSON file
 *
 * @param inputPath - Input file path (defaults to news/articles-metadata.json)
 * @returns Metadata database or null if file doesn't exist
 */
export function readMetadataDatabase(
  inputPath: string = METADATA_DB_PATH
): NewsMetadataDatabase | null {
  if (!fs.existsSync(inputPath)) {
    return null;
  }
 
  const content = fs.readFileSync(inputPath, 'utf-8');
  return JSON.parse(content) as NewsMetadataDatabase;
}
 
/**
 * Update metadata database by rescanning the news directory
 *
 * @param newsDir - News directory to scan
 * @param outputPath - Output path for metadata JSON
 * @returns Updated metadata database
 */
export function updateMetadataDatabase(
  newsDir: string = NEWS_DIR,
  outputPath: string = METADATA_DB_PATH
): NewsMetadataDatabase {
  const database = buildMetadataDatabase(newsDir);
  writeMetadataDatabase(database, outputPath);
  return database;
}