Source Code

/ src / blog / loader.py

import markdown
import yaml
from pathlib import Path
import re
import frontmatter

from django.conf import settings
from blog.models import Author, Column, Post

from .crypto import encrypt_for_cryptojs

def slugify(text):
    text = text.lower()
    text = text.replace(".", "")
    text = re.sub(r"[^a-z0-9]+", "-", text)
    return text.strip("-")


def load_yaml(path: str) -> dict:
    """
    Load yaml file and return a dict
    """
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def load_authors():
    authors = load_yaml(settings.CONFIG_FILE["authors"])

    for author_id, author in authors.items():
        Author.objects.update_or_create(
            slug=author_id,
            defaults={
                "name": author.get("name", author_id),
                "description": author.get("description", ""),
                "avatar": author.get("avatar", ""),
                "url": author.get("url", ""),
                "featured": author.get("featured", False),
                "is_staff": author.get("is_staff", False)
            },
        )

def load_columns():
    columns = load_yaml(settings.CONFIG_FILE["columns"])

    for column_id, column in columns.items():
        Column.objects.update_or_create(
            slug=column_id,
            defaults={
                "name": column.get("name", column_id),
                "description": column.get("description", ""),
                "unlisted": column.get("unlisted", False),
                "icon": column.get("icon", "")
            }
        )


def load_single_post(md_path):
    md_path = Path(md_path)
    if not md_path.exists():
        print(f"Skipping missing file {md_path}")
        return

    fm = frontmatter.load(md_path)



    title = fm.get("title") or md_path.stem
    description = fm.get("description", "")
    slug = slugify(fm.get("slug") or title)

    date_val = fm.get("date")
    if not date_val:
        print(f"Skipping {md_path}: no date")
        return



    authors_list = fm.get("authors")
    if not authors_list:
        print(f"Skipping {md_path}: no authors")
        return

    author_id = authors_list[0]
    try:
        author = Author.objects.get(slug=author_id)
    except Author.DoesNotExist:
        print(f"Skipping {md_path}: unknown author '{author_id}'")
        return



    categories = fm.get("categories")
    if not categories:
        print(f"Skipping {md_path}: no categories")
        return

    column_name = categories[0]
    column, _ = Column.objects.get_or_create(
        name=column_name,
        slug=slugify(column_name),
    )



    source = fm.get("source")
    image = fm.get("image")
    disable_comments = fm.get("disable_comments")



    content_raw = fm.content
    aside_html = None
    if "<!-- ASIDE -->" in content_raw and "<!-- /ASIDE -->" in content_raw:
        before, remainder = content_raw.split("<!-- ASIDE -->", 1)
        aside, after = remainder.split("<!-- /ASIDE -->", 1)

        aside_html = aside.strip()
        content_raw = after.strip()

    content_html = markdown.markdown(
        content_raw,
        extensions=["fenced_code", "tables"],
    )
    encrypt_password = fm.get("encrypt_password")

    encrypted_content = None
    encrypted_aside = None

    if encrypt_password:
        encrypted_content = encrypt_for_cryptojs(content_html, encrypt_password)
        content_html = ""

        if aside_html:
            encrypted_aside = encrypt_for_cryptojs(aside_html, encrypt_password)
            aside_html = ""



    Post.objects.update_or_create(
        slug=slug,
        date=date_val,
        defaults={
            "title": title,
            "description": description,
            "author": author,
            "column": column,
            "source": source,
            "image": image,
            "markdown_path": str(md_path),
            "content_html": content_html,
            "aside_html": aside_html,
            "encrypted_content": None if not encrypted_content else encrypted_content,
            "encrypted_aside": None if not encrypted_aside else encrypted_aside,
            "disable_comments": True if disable_comments else False
        },
    )

    print(f"Loaded: {title}")


def load_posts():
    base = Path("content/blog")

    for md_path in base.rglob("*.md"):
        load_single_post(md_path)