From d484835f5b1b9ab5c86793957dd9c0d8d991c8b4 Mon Sep 17 00:00:00 2001 From: mi Date: Thu, 13 Nov 2025 14:08:07 +1000 Subject: [PATCH] :memo: api docs --- app.py | 6 +- comics_data.py | 4 + static/css/style.css | 27 ++++++- static/openapi.yaml | 170 +++++++++++++++++++++++++++++++++++++++++++ templates/base.html | 3 + 5 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 static/openapi.yaml diff --git a/app.py b/app.py index c8352d2..f78a1da 100644 --- a/app.py +++ b/app.py @@ -4,7 +4,8 @@ from flask import Flask, render_template, abort, jsonify, request from comics_data import ( COMICS, FULL_WIDTH_DEFAULT, PLAIN_DEFAULT, HEADER_IMAGE, FOOTER_IMAGE, COMPACT_FOOTER, USE_COMIC_NAV_ICONS, USE_HEADER_NAV_ICONS, - USE_FOOTER_SOCIAL_ICONS, SOCIAL_INSTAGRAM, SOCIAL_YOUTUBE, SOCIAL_EMAIL + USE_FOOTER_SOCIAL_ICONS, SOCIAL_INSTAGRAM, SOCIAL_YOUTUBE, SOCIAL_EMAIL, + API_SPEC_LINK ) import markdown @@ -26,7 +27,8 @@ def inject_global_settings(): 'use_footer_social_icons': USE_FOOTER_SOCIAL_ICONS, 'social_instagram': SOCIAL_INSTAGRAM, 'social_youtube': SOCIAL_YOUTUBE, - 'social_email': SOCIAL_EMAIL + 'social_email': SOCIAL_EMAIL, + 'api_spec_link': API_SPEC_LINK } diff --git a/comics_data.py b/comics_data.py index 8e89f8f..141ed6d 100644 --- a/comics_data.py +++ b/comics_data.py @@ -40,6 +40,10 @@ SOCIAL_INSTAGRAM = None # e.g., 'https://instagram.com/yourhandle' SOCIAL_YOUTUBE = None # e.g., 'https://youtube.com/@yourchannel' SOCIAL_EMAIL = None # e.g., 'mailto:your@email.com' +# API documentation link - set to None to hide the link +# Path is relative to static/ directory +API_SPEC_LINK = 'openapi.yaml' # Set to None to disable + COMICS = [ { 'number': 1, diff --git a/static/css/style.css b/static/css/style.css index 840ccc4..0de4670 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -686,6 +686,18 @@ footer { letter-spacing: var(--letter-spacing-tight); } +.footer-bottom a.api-link { + color: var(--color-text); + text-decoration: none; + font-size: var(--font-size-md); + text-transform: uppercase; + letter-spacing: var(--letter-spacing-tight); +} + +.footer-bottom a.api-link:hover { + text-decoration: underline; +} + /* Compact Footer Mode */ footer.compact-footer { border-top: none; @@ -698,6 +710,7 @@ footer.compact-footer .container { align-items: center; justify-content: center; gap: var(--space-md); + flex-wrap: wrap; } footer.compact-footer .footer-content { @@ -705,7 +718,7 @@ footer.compact-footer .footer-content { align-items: center; gap: var(--space-md); margin-bottom: 0; - flex-wrap: nowrap; + flex-wrap: wrap; } footer.compact-footer .footer-section { @@ -745,6 +758,8 @@ footer.compact-footer .footer-bottom { padding-top: 0; display: flex; align-items: center; + flex-wrap: wrap; + gap: var(--space-xs); } footer.compact-footer .footer-bottom::before { @@ -758,6 +773,16 @@ footer.compact-footer .footer-bottom p { margin: 0; } +footer.compact-footer .footer-bottom a.api-link { + font-size: var(--font-size-sm); +} + +footer.compact-footer .footer-bottom a.api-link::before { + content: '|'; + margin: 0 var(--space-sm); + color: var(--color-text-muted); +} + /* Add separator between sections in compact mode */ footer.compact-footer .footer-section:not(:last-child)::after { content: '|'; diff --git a/static/openapi.yaml b/static/openapi.yaml new file mode 100644 index 0000000..6caa3dc --- /dev/null +++ b/static/openapi.yaml @@ -0,0 +1,170 @@ +openapi: 3.0.3 +info: + title: Sunday Comics API + description: API for accessing webcomic data from the Sunday Comics platform + version: 1.0.0 + contact: + url: http://127.0.0.1:3000 + +servers: + - url: http://127.0.0.1:3000 + description: Development server + - url: https://your-production-domain.com + description: Production server (update with your actual domain) + +paths: + /api/comics: + get: + summary: Get all comics + description: Returns a list of all comics with enriched metadata including formatted dates and author notes + operationId: getAllComics + tags: + - Comics + responses: + '200': + description: Successful response with array of comics + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Comic' + example: + - number: 1 + title: "First Comic" + filename: "comic-001.jpg" + mobile_filename: "comic-001-mobile.jpg" + date: "2025-01-01" + alt_text: "The very first comic" + author_note: "This is where your comic journey begins!" + full_width: true + plain: true + formatted_date: "Wednesday, January 1, 2025" + author_note_is_html: false + - 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 + + /api/comics/{comic_id}: + get: + summary: Get a specific comic + description: Returns a single comic by its number/ID with enriched metadata + operationId: getComicById + tags: + - Comics + parameters: + - name: comic_id + in: path + description: Comic number/ID + required: true + schema: + type: integer + minimum: 1 + example: 1 + responses: + '200': + description: Successful response with comic data + content: + application/json: + schema: + $ref: '#/components/schemas/Comic' + example: + number: 1 + title: "First Comic" + filename: "comic-001.jpg" + mobile_filename: "comic-001-mobile.jpg" + date: "2025-01-01" + alt_text: "The very first comic" + author_note: "This is where your comic journey begins!" + full_width: true + plain: true + formatted_date: "Wednesday, January 1, 2025" + author_note_is_html: false + '404': + description: Comic not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "Comic not found" + +components: + schemas: + Comic: + type: object + required: + - number + - filename + - date + - alt_text + - full_width + - plain + - formatted_date + - author_note_is_html + properties: + number: + type: integer + description: Sequential comic number (unique identifier) + minimum: 1 + example: 1 + title: + type: string + description: Comic title (optional, defaults to "#X" if not provided) + example: "First Comic" + filename: + type: string + description: Image filename in static/images/comics/ + example: "comic-001.jpg" + mobile_filename: + type: string + description: Optional mobile version of the comic image + example: "comic-001-mobile.jpg" + date: + type: string + format: date + description: Publication date in YYYY-MM-DD format + example: "2025-01-01" + alt_text: + type: string + description: Accessibility text for the comic image + example: "The very first comic" + author_note: + type: string + description: Author's note about the comic (plain text or HTML from markdown) + example: "This is where your comic journey begins!" + full_width: + type: boolean + description: Whether the comic should display in full-width mode (computed from global default and per-comic override) + example: true + plain: + type: boolean + description: Whether the comic should display in plain mode without header/borders (computed from global default and per-comic override) + example: true + formatted_date: + type: string + description: Human-readable formatted date (computed field) + example: "Wednesday, January 1, 2025" + author_note_is_html: + type: boolean + description: Indicates whether author_note contains HTML (from markdown) or plain text (computed field) + example: false + + Error: + type: object + required: + - error + properties: + error: + type: string + description: Error message + example: "Comic not found" + +tags: + - name: Comics + description: Operations for accessing comic data diff --git a/templates/base.html b/templates/base.html index b7bcb90..2a4ddb9 100644 --- a/templates/base.html +++ b/templates/base.html @@ -130,6 +130,9 @@