File size: 5,707 Bytes
2ee7e68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25a69de
2ee7e68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25a69de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ee7e68
 
 
 
 
 
 
 
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env python3
"""
Scan the drafts/ directory and seed every markdown file into the database.
Also seeds a few hardcoded sample posts if no drafts exist.
Safe to re-run — skips existing slugs.
"""

from __future__ import annotations

import re
import sys
import os
from datetime import datetime
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from app import app, db, Post, Subscriber, prepare_post_content, generate_slug, calculate_content_hash

DRAFTS_DIR = PROJECT_ROOT / "drafts"

# Simple YAML frontmatter parser (avoids pyyaml dependency)
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
HEADING_RE = re.compile(r"^#\s+(.+)$", re.MULTILINE)
DATE_PREFIX_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})-(.+)$")


def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
    """Return (metadata_dict, body_without_frontmatter)."""
    match = FRONTMATTER_RE.match(text)
    if not match:
        return {}, text

    raw = match.group(1)
    body = text[match.end():]
    meta: dict[str, str] = {}
    for line in raw.splitlines():
        if ":" in line:
            key, _, value = line.partition(":")
            meta[key.strip().lower()] = value.strip().strip('"').strip("'")
    return meta, body


def slug_from_filename(filename: str) -> str:
    """Derive a URL slug from the markdown filename."""
    stem = Path(filename).stem
    m = DATE_PREFIX_RE.match(stem)
    if m:
        return m.group(2)
    return stem


def date_from_filename(filename: str) -> datetime | None:
    m = DATE_PREFIX_RE.match(Path(filename).stem)
    if m:
        try:
            return datetime.strptime(m.group(1), "%Y-%m-%d")
        except ValueError:
            return None
    return None


def title_from_content(meta: dict[str, str], body: str, slug: str) -> str:
    """Extract title from frontmatter, first heading, or slug."""
    if meta.get("title"):
        return meta["title"]
    heading_match = HEADING_RE.search(body)
    if heading_match:
        return heading_match.group(1).strip()
    return slug.replace("-", " ").title()


def summary_from_content(body: str, max_len: int = 300) -> str:
    """Extract a plain-text summary from the body."""
    # Strip markdown formatting for a clean summary
    text = re.sub(r'#+ ', '', body)
    text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text)
    text = re.sub(r'\*([^*]+)\*', r'\1', text)
    text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text)
    text = re.sub(r'[-*] ', '', text)
    text = re.sub(r'\n+', ' ', text).strip()
    if len(text) > max_len:
        text = text[:max_len].rsplit(' ', 1)[0] + '...'
    return text


def seed_from_drafts():
    """Scan drafts/ and add all markdown files to the database."""
    if not DRAFTS_DIR.is_dir():
        print(f"  No drafts directory found at {DRAFTS_DIR}")
        return 0

    md_files = sorted(DRAFTS_DIR.glob("*.md"))
    if not md_files:
        print("  No markdown files found in drafts/")
        return 0

    added = 0
    skipped = 0

    for md_path in md_files:
        raw = md_path.read_text(encoding="utf-8")
        meta, body = parse_frontmatter(raw)

        slug = slug_from_filename(md_path.name)
        title = title_from_content(meta, body, slug)
        category = meta.get("category", "Excavation Reports")
        source_url = meta.get("source_url", "")
        source_name = meta.get("source_name", "")

        pub_date = date_from_filename(md_path.name)
        if not pub_date and meta.get("date"):
            try:
                pub_date = datetime.strptime(meta["date"], "%Y-%m-%d")
            except ValueError:
                pub_date = None
        if not pub_date:
            pub_date = datetime.utcnow()

        # Skip if slug already exists
        if Post.query.filter_by(slug=slug).first():
            print(f"  [skip] {slug}")
            skipped += 1
            continue

        # Render markdown to HTML
        rendered = prepare_post_content(body.strip())
        content_hash = calculate_content_hash(body.strip())
        summary = summary_from_content(body)

        post = Post(
            title=title,
            slug=slug,
            content=rendered,
            summary=summary,
            pub_date=pub_date,
            category=category,
            source_url=source_url,
            source_name=source_name,
            published=True,
            content_hash=content_hash,
        )
        db.session.add(post)
        db.session.commit()
        print(f"  [added] {slug}{title}")
        added += 1

        # Notify subscribers about the new article (if email is configured)
        if os.environ.get('BREVO_API_KEY') and os.environ.get('NOTIFY_ON_SEED', '').lower() == 'true':
            try:
                from email_service import send_new_article_notification
                subs = [s.email for s in Subscriber.query.filter_by(confirmed=True).all()]
                if subs:
                    sent, failed = send_new_article_notification(
                        subscribers=subs,
                        post_title=title,
                        post_slug=slug,
                        post_summary=summary,
                        post_category=category,
                    )
                    print(f"    [email] Notified {sent} subscribers ({failed} failed)")
            except Exception as exc:
                print(f"    [email] Notification failed: {exc}")

    print(f"\nSeeding complete: {added} added, {skipped} skipped.")
    return added


if __name__ == "__main__":
    with app.app_context():
        db.create_all()
        seed_from_drafts()