📝 consolidate multiple list endpoints for comics

This commit is contained in:
mi
2025-11-15 20:40:18 +10:00
parent 52b80563ba
commit 3153455355
3 changed files with 209 additions and 48 deletions

40
app.py
View File

@@ -318,24 +318,19 @@ def terms():
@app.route('/api/comics') @app.route('/api/comics')
def api_comics(): def api_comics():
"""API endpoint - returns all comics as JSON""" """API endpoint - returns all comics as JSON (optionally paginated with sections)"""
return jsonify([enrich_comic(comic) for comic in COMICS]) # Check for pagination parameters
page = request.args.get('page', type=int)
per_page = request.args.get('per_page', type=int)
group_by_section = request.args.get('group_by_section', 'false').lower() in ('true', '1', 'yes')
# If no pagination requested, return simple array (backward compatible)
if page is None and per_page is None and not group_by_section:
return jsonify([enrich_comic(comic) for comic in COMICS])
@app.route('/api/comics/<int:comic_id>') # Pagination requested - return paginated response
def api_comic(comic_id): page = page or 1
"""API endpoint - returns a specific comic as JSON""" per_page = per_page or 24
comic = get_comic_by_number(comic_id)
if not comic:
return jsonify({'error': 'Comic not found'}), 404
return jsonify(comic)
@app.route('/api/archive')
def api_archive():
"""API endpoint - returns paginated archive data"""
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 24, type=int)
# Limit per_page to reasonable values # Limit per_page to reasonable values
per_page = min(max(per_page, 1), 100) per_page = min(max(per_page, 1), 100)
@@ -343,8 +338,8 @@ def api_archive():
# Reverse order to show newest first # Reverse order to show newest first
all_comics = [enrich_comic(comic) for comic in reversed(COMICS)] all_comics = [enrich_comic(comic) for comic in reversed(COMICS)]
# Group by section if enabled # Group by section if enabled globally or requested via parameter
sections = group_comics_by_section(all_comics) sections = group_comics_by_section(all_comics) if (SECTIONS_ENABLED or group_by_section) else [(None, all_comics)]
# Calculate pagination # Calculate pagination
total_comics = len(all_comics) total_comics = len(all_comics)
@@ -388,6 +383,15 @@ def api_archive():
}) })
@app.route('/api/comics/<int:comic_id>')
def api_comic(comic_id):
"""API endpoint - returns a specific comic as JSON"""
comic = get_comic_by_number(comic_id)
if not comic:
return jsonify({'error': 'Comic not found'}), 404
return jsonify(comic)
@app.route('/sitemap.xml') @app.route('/sitemap.xml')
def sitemap(): def sitemap():
"""Serve the static sitemap.xml file""" """Serve the static sitemap.xml file"""

View File

@@ -41,7 +41,7 @@
try { try {
currentPage++; currentPage++;
const response = await fetch(`/api/archive?page=${currentPage}&per_page=${perPage}`); const response = await fetch(`/api/comics?page=${currentPage}&per_page=${perPage}`);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);

View File

@@ -16,39 +16,107 @@ paths:
/api/comics: /api/comics:
get: get:
summary: Get all comics summary: Get all comics
description: Returns a list of all comics with enriched metadata including formatted dates and author notes description: |
Returns all comics with enriched metadata. Supports optional pagination and section grouping.
**Without pagination parameters:** Returns a simple array of all comics (newest first when using pagination, original order otherwise).
**With pagination parameters:** Returns paginated response with section grouping (if enabled globally or via `group_by_section` parameter).
operationId: getAllComics operationId: getAllComics
tags: tags:
- Comics - Comics
parameters:
- name: page
in: query
description: Page number for pagination (1-indexed). When provided, triggers paginated response format.
required: false
schema:
type: integer
minimum: 1
default: 1
example: 1
- name: per_page
in: query
description: Number of comics per page (max 100). When provided, triggers paginated response format.
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 24
example: 24
- name: group_by_section
in: query
description: Force section grouping in response (even when SECTIONS_ENABLED is false). When true, triggers paginated response format.
required: false
schema:
type: boolean
default: false
example: false
responses: responses:
'200': '200':
description: Successful response with array of comics description: Successful response
content: content:
application/json: application/json:
schema: schema:
type: array oneOf:
items: - type: array
$ref: '#/components/schemas/Comic' description: Simple array response (when no pagination parameters provided)
example: items:
- number: 1 $ref: '#/components/schemas/Comic'
title: "First Comic" - $ref: '#/components/schemas/PaginatedComicsResponse'
filename: "comic-001.jpg" examples:
mobile_filename: "comic-001-mobile.jpg" simpleArray:
date: "2025-01-01" summary: Simple array response (default)
alt_text: "The very first comic" value:
author_note: "This is where your comic journey begins!" - number: 1
full_width: true title: "First Comic"
plain: true filename: "comic-001.jpg"
formatted_date: "Wednesday, January 1, 2025" mobile_filename: "comic-001-mobile.jpg"
author_note_is_html: false date: "2025-01-01"
- number: 2 alt_text: "The very first comic"
filename: "comic-002.jpg" author_note: "This is where your comic journey begins!"
date: "2025-01-08" full_width: true
alt_text: "The second comic" plain: true
full_width: true formatted_date: "Wednesday, January 1, 2025"
plain: true author_note_is_html: false
formatted_date: "Wednesday, January 8, 2025" - number: 2
author_note_is_html: false filename: "comic-002.jpg"
date: "2025-01-08"
alt_text: "The second comic"
full_width: true
plain: true
formatted_date: "Wednesday, January 8, 2025"
author_note_is_html: false
paginatedResponse:
summary: Paginated response (when using page/per_page parameters)
value:
sections:
- section_title: "Chapter 1"
comics:
- number: 2
filename: "comic-002.jpg"
date: "2025-01-08"
alt_text: "The second comic"
full_width: true
plain: true
formatted_date: "Wednesday, January 8, 2025"
author_note_is_html: false
- section_title: null
comics:
- number: 1
title: "First Comic"
filename: "comic-001.jpg"
date: "2025-01-01"
alt_text: "The very first comic"
full_width: true
plain: true
formatted_date: "Wednesday, January 1, 2025"
author_note_is_html: false
page: 1
per_page: 24
total_comics: 2
has_more: false
/api/comics/{comic_id}: /api/comics/{comic_id}:
get: get:
@@ -107,6 +175,9 @@ components:
- plain - plain
- formatted_date - formatted_date
- author_note_is_html - author_note_is_html
- filenames
- alt_texts
- is_multi_image
properties: properties:
number: number:
type: integer type: integer
@@ -118,9 +189,20 @@ components:
description: Comic title (optional, defaults to "#X" if not provided) description: Comic title (optional, defaults to "#X" if not provided)
example: "First Comic" example: "First Comic"
filename: filename:
type: string oneOf:
description: Image filename in static/images/comics/ - type: string
description: Single image filename in static/images/comics/
- type: array
description: Multiple image filenames for webtoon-style comics
items:
type: string
example: "comic-001.jpg" example: "comic-001.jpg"
filenames:
type: array
description: Normalized array of image filenames (computed field, always an array even for single images)
items:
type: string
example: ["comic-001.jpg"]
mobile_filename: mobile_filename:
type: string type: string
description: Optional mobile version of the comic image description: Optional mobile version of the comic image
@@ -131,13 +213,36 @@ components:
description: Publication date in YYYY-MM-DD format description: Publication date in YYYY-MM-DD format
example: "2025-01-01" example: "2025-01-01"
alt_text: alt_text:
type: string oneOf:
description: Accessibility text for the comic image - type: string
description: Accessibility text for single image or shared text for all images
- type: array
description: Individual accessibility text for each image in multi-image comics
items:
type: string
example: "The very first comic" example: "The very first comic"
alt_texts:
type: array
description: Normalized array of alt texts matching filenames (computed field)
items:
type: string
example: ["The very first comic"]
is_multi_image:
type: boolean
description: Indicates if this is a multi-image comic (computed field)
example: false
author_note: author_note:
type: string type: string
description: Author's note about the comic (plain text or HTML from markdown) description: Author's note about the comic (plain text or HTML from markdown)
example: "This is where your comic journey begins!" example: "This is where your comic journey begins!"
author_note_md:
type: string
description: Filename or path to markdown file for author note
example: "2025-01-01.md"
section:
type: string
description: Section/chapter title (appears on archive page when SECTIONS_ENABLED is true)
example: "Chapter 1: Origins"
full_width: full_width:
type: boolean type: boolean
description: Whether the comic should display in full-width mode (computed from global default and per-comic override) description: Whether the comic should display in full-width mode (computed from global default and per-comic override)
@@ -155,6 +260,58 @@ components:
description: Indicates whether author_note contains HTML (from markdown) or plain text (computed field) description: Indicates whether author_note contains HTML (from markdown) or plain text (computed field)
example: false example: false
ComicSection:
type: object
required:
- section_title
- comics
properties:
section_title:
type: string
nullable: true
description: Section/chapter title (null for comics without a section)
example: "Chapter 1"
comics:
type: array
description: Comics in this section
items:
$ref: '#/components/schemas/Comic'
PaginatedComicsResponse:
type: object
required:
- sections
- page
- per_page
- total_comics
- has_more
properties:
sections:
type: array
description: Comics grouped by section
items:
$ref: '#/components/schemas/ComicSection'
page:
type: integer
description: Current page number
minimum: 1
example: 1
per_page:
type: integer
description: Number of comics per page
minimum: 1
maximum: 100
example: 24
total_comics:
type: integer
description: Total number of comics across all pages
minimum: 0
example: 100
has_more:
type: boolean
description: Whether there are more pages available
example: true
Error: Error:
type: object type: object
required: required: