diff --git a/src/epub_builder.py b/src/epub_builder.py index a66657a..ca227bc 100644 --- a/src/epub_builder.py +++ b/src/epub_builder.py @@ -64,6 +64,7 @@ def build_epub( article_ids: list[int], cover_path: str, output_dir: str, + issue_type: str = "weekly", ) -> str: os.makedirs(output_dir, exist_ok=True) @@ -74,10 +75,20 @@ def build_epub( .all() ) - title = ( - f"Plymouth Independent \u2014 " - f"Week of {week_start.strftime('%b %d')}\u2013{week_end.strftime('%b %d, %Y')}" - ) + if issue_type == "single_article" and len(articles) == 1: + title = f"Plymouth Independent \u2014 {articles[0].title}" + elif issue_type == "multi_week": + w1 = week_start.isocalendar()[1] + w2 = week_end.isocalendar()[1] + title = ( + f"Plymouth Independent \u2014 " + f"Weeks {w1}\u2013{w2}, {week_start.strftime('%b %d')}\u2013{week_end.strftime('%b %d, %Y')}" + ) + else: + title = ( + f"Plymouth Independent \u2014 " + f"Week of {week_start.strftime('%b %d')}\u2013{week_end.strftime('%b %d, %Y')}" + ) book = epub.EpubBook() book.set_identifier(f"pi-{week_start.isoformat()}") diff --git a/src/routes/publish.py b/src/routes/publish.py index 39df648..31c1c29 100644 --- a/src/routes/publish.py +++ b/src/routes/publish.py @@ -1,6 +1,7 @@ import json from datetime import date, timedelta -from flask import Blueprint, render_template, request, redirect, url_for, flash +from calendar import monthrange +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from app import db from src.models import Article, Issue @@ -11,19 +12,64 @@ import config publish_bp = Blueprint("publish", __name__) +def _get_week_bounds(d: date) -> tuple[date, date]: + week_start = d - timedelta(days=d.weekday()) + week_end = week_start + timedelta(days=6) + return week_start, week_end + + +def _calendar_data(year: int, month: int) -> list[dict]: + """Build calendar grid data for a given month.""" + first_day = date(year, month, 1) + _, days_in_month = monthrange(year, month) + + weeks = [] + current = first_day - timedelta(days=first_day.weekday()) + + while current.month <= month or (current + timedelta(days=6)).month <= month or current < first_day: + week_start = current + week_end = current + timedelta(days=6) + iso_week = week_start.isocalendar()[1] + + article_count = Article.query.filter( + Article.pub_date >= str(week_start), + Article.pub_date < str(week_end + timedelta(days=1)), + ).count() + + days = [] + for i in range(7): + d = current + timedelta(days=i) + days.append({ + "day": d.day, + "date": d.isoformat(), + "in_month": d.month == month, + }) + + weeks.append({ + "iso_week": iso_week, + "week_start": week_start.isoformat(), + "week_end": week_end.isoformat(), + "days": days, + "article_count": article_count, + }) + + current += timedelta(days=7) + if current.month > month and current.year >= year: + if not any(d["in_month"] for d in days): + weeks.pop() + break + + return weeks + + @publish_bp.route("/publish", methods=["GET"]) def index(): - week_str = request.args.get("week") - if week_str: - try: - year, week_num = week_str.split("-W") - week_start = date.fromisocalendar(int(year), int(week_num), 1) - except (ValueError, TypeError): - week_start = date.today() - timedelta(days=date.today().weekday()) - else: - week_start = date.today() - timedelta(days=date.today().weekday()) + today = date.today() + cal_year = request.args.get("cal_year", today.year, type=int) + cal_month = request.args.get("cal_month", today.month, type=int) - week_end = week_start + timedelta(days=6) + week_start, week_end = _get_week_bounds(today) + calendar_weeks = _calendar_data(cal_year, cal_month) articles = ( Article.query @@ -35,20 +81,75 @@ def index(): .all() ) + all_articles = ( + Article.query + .order_by(Article.pub_date.desc()) + .all() + ) + return render_template( "publish.html", articles=articles, + all_articles=all_articles, week_start=week_start, week_end=week_end, - week_str=f"{week_start.year}-W{week_start.isocalendar()[1]:02d}", + calendar_weeks=calendar_weeks, + cal_year=cal_year, + cal_month=cal_month, + cal_month_name=date(cal_year, cal_month, 1).strftime("%B %Y"), ) +@publish_bp.route("/publish/articles", methods=["GET"]) +def articles_api(): + start = request.args.get("start") + end = request.args.get("end") + if not start or not end: + return jsonify([]) + + try: + start_date = date.fromisoformat(start) + end_date = date.fromisoformat(end) + except ValueError: + return jsonify([]) + + articles = ( + Article.query + .filter( + Article.pub_date >= str(start_date), + Article.pub_date < str(end_date + timedelta(days=1)), + ) + .order_by(Article.pub_date.asc()) + .all() + ) + + return jsonify([ + { + "id": a.id, + "title": a.title, + "author": a.author, + "pub_date": a.pub_date.strftime("%b %d, %Y"), + "categories": json.loads(a.categories), + } + for a in articles + ]) + + +@publish_bp.route("/publish/calendar", methods=["GET"]) +def calendar_api(): + year = request.args.get("year", type=int) + month = request.args.get("month", type=int) + if not year or not month: + return jsonify([]) + return jsonify(_calendar_data(year, month)) + + @publish_bp.route("/publish", methods=["POST"]) def create_issue(): week_start_str = request.form.get("week_start") week_end_str = request.form.get("week_end") cover_method = request.form.get("cover_method", "text") + issue_type = request.form.get("issue_type", "weekly") included_ids = request.form.getlist("article_ids", type=int) if not included_ids: @@ -58,17 +159,6 @@ def create_issue(): week_start = date.fromisoformat(week_start_str) week_end = date.fromisoformat(week_end_str) - all_week_articles = ( - Article.query - .filter( - Article.pub_date >= str(week_start), - Article.pub_date < str(week_end + timedelta(days=1)), - ) - .all() - ) - all_ids = {a.id for a in all_week_articles} - excluded_ids = list(all_ids - set(included_ids)) - headlines = [ a.title for a in Article.query.filter(Article.id.in_(included_ids)) .order_by(Article.pub_date.asc()).all() @@ -84,12 +174,24 @@ def create_issue(): headlines, categories_list ) epub_path = build_epub( - week_start, week_end, included_ids, cover_path, config.ISSUES_DIR + week_start, week_end, included_ids, cover_path, + config.ISSUES_DIR, issue_type=issue_type ) except Exception as e: flash(f"Error generating issue: {e}", "error") return redirect(url_for("publish.index")) + all_week_articles = ( + Article.query + .filter( + Article.pub_date >= str(week_start), + Article.pub_date < str(week_end + timedelta(days=1)), + ) + .all() + ) + all_ids = {a.id for a in all_week_articles} + excluded_ids = list(all_ids - set(included_ids)) + issue = Issue( week_start=week_start, week_end=week_end, @@ -99,6 +201,7 @@ def create_issue(): article_ids=json.dumps(included_ids), excluded_article_ids=json.dumps(excluded_ids), status="published", + issue_type=issue_type, ) db.session.add(issue) db.session.commit() diff --git a/templates/publish.html b/templates/publish.html index aa9acc6..1b1d437 100644 --- a/templates/publish.html +++ b/templates/publish.html @@ -3,73 +3,272 @@ {% block content %}
{{ week_start.strftime('%b %d') }} – {{ week_end.strftime('%b %d, %Y') }} · {{ articles|length }} articles
+ +No articles found for this week. Try fetching articles first from the Dashboard.
-{% endif %} + +