📝 api docs
This commit is contained in:
6
app.py
6
app.py
@@ -4,7 +4,8 @@ from flask import Flask, render_template, abort, jsonify, request
|
|||||||
from comics_data import (
|
from comics_data import (
|
||||||
COMICS, FULL_WIDTH_DEFAULT, PLAIN_DEFAULT, HEADER_IMAGE, FOOTER_IMAGE,
|
COMICS, FULL_WIDTH_DEFAULT, PLAIN_DEFAULT, HEADER_IMAGE, FOOTER_IMAGE,
|
||||||
COMPACT_FOOTER, USE_COMIC_NAV_ICONS, USE_HEADER_NAV_ICONS,
|
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
|
import markdown
|
||||||
|
|
||||||
@@ -26,7 +27,8 @@ def inject_global_settings():
|
|||||||
'use_footer_social_icons': USE_FOOTER_SOCIAL_ICONS,
|
'use_footer_social_icons': USE_FOOTER_SOCIAL_ICONS,
|
||||||
'social_instagram': SOCIAL_INSTAGRAM,
|
'social_instagram': SOCIAL_INSTAGRAM,
|
||||||
'social_youtube': SOCIAL_YOUTUBE,
|
'social_youtube': SOCIAL_YOUTUBE,
|
||||||
'social_email': SOCIAL_EMAIL
|
'social_email': SOCIAL_EMAIL,
|
||||||
|
'api_spec_link': API_SPEC_LINK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ SOCIAL_INSTAGRAM = None # e.g., 'https://instagram.com/yourhandle'
|
|||||||
SOCIAL_YOUTUBE = None # e.g., 'https://youtube.com/@yourchannel'
|
SOCIAL_YOUTUBE = None # e.g., 'https://youtube.com/@yourchannel'
|
||||||
SOCIAL_EMAIL = None # e.g., 'mailto:your@email.com'
|
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 = [
|
COMICS = [
|
||||||
{
|
{
|
||||||
'number': 1,
|
'number': 1,
|
||||||
|
|||||||
@@ -686,6 +686,18 @@ footer {
|
|||||||
letter-spacing: var(--letter-spacing-tight);
|
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 */
|
/* Compact Footer Mode */
|
||||||
footer.compact-footer {
|
footer.compact-footer {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
@@ -698,6 +710,7 @@ footer.compact-footer .container {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--space-md);
|
gap: var(--space-md);
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer.compact-footer .footer-content {
|
footer.compact-footer .footer-content {
|
||||||
@@ -705,7 +718,7 @@ footer.compact-footer .footer-content {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-md);
|
gap: var(--space-md);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer.compact-footer .footer-section {
|
footer.compact-footer .footer-section {
|
||||||
@@ -745,6 +758,8 @@ footer.compact-footer .footer-bottom {
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
footer.compact-footer .footer-bottom::before {
|
footer.compact-footer .footer-bottom::before {
|
||||||
@@ -758,6 +773,16 @@ footer.compact-footer .footer-bottom p {
|
|||||||
margin: 0;
|
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 */
|
/* Add separator between sections in compact mode */
|
||||||
footer.compact-footer .footer-section:not(:last-child)::after {
|
footer.compact-footer .footer-section:not(:last-child)::after {
|
||||||
content: '|';
|
content: '|';
|
||||||
|
|||||||
170
static/openapi.yaml
Normal file
170
static/openapi.yaml
Normal file
@@ -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
|
||||||
@@ -130,6 +130,9 @@
|
|||||||
|
|
||||||
<div class="footer-bottom">
|
<div class="footer-bottom">
|
||||||
<p>© 2025 Sunday Comics. All rights reserved.</p>
|
<p>© 2025 Sunday Comics. All rights reserved.</p>
|
||||||
|
{% if api_spec_link %}
|
||||||
|
<a href="{{ url_for('static', filename=api_spec_link) }}" class="api-link">API</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user