feat: quotes-ui

This commit is contained in:
Timothy Jaeryang Baek
2025-09-25 15:34:42 -05:00
parent 36eb2b9ed3
commit d5c1547c05
5 changed files with 236 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/
**/.DS_Store
**/__pycache__
**/.venv
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/bin
**/charts
**/docker-compose*
**/compose.y*ml
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+51
View File
@@ -0,0 +1,51 @@
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
ARG PYTHON_VERSION=3.10.12
FROM python:${PYTHON_VERSION}-slim as base
# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1
# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them into
# into this layer.
RUN --mount=type=cache,target=/root/.cache/pip \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
python -m pip install -r requirements.txt
# Switch to the non-privileged user to run the application.
USER appuser
# Copy the source code into the container.
COPY . .
# Expose the port that the application listens on.
EXPOSE 8000
# Run the application.
CMD uvicorn 'main:app' --host=0.0.0.0 --port=8000
+7
View File
@@ -0,0 +1,7 @@
services:
server:
build:
context: .
ports:
- 8000:8000
+137
View File
@@ -0,0 +1,137 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, Field
from typing import List, Optional
app = FastAPI(title="Quote Display API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["Content-Disposition"],
)
class Quote(BaseModel):
text: str
author: str
class QuoteSet(BaseModel):
title: str = "Quotes"
quotes: List[Quote]
def quote_html(title: str, quotes: List[dict]) -> str:
quote_items = ""
for quote in quotes:
quote_items += f"""
<div class="quote">
<p>"{quote['text']}"</p>
<span>— {quote['author']}</span>
</div>
"""
return f"""
<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
<style>
body {{
font-family: system-ui, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
}}
.container {{
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
max-width: 600px;
width: 100%;
}}
h1 {{
text-align: center;
color: #2c3e50;
margin-bottom: 30px;
}}
.quote {{
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
margin: 20px 0;
text-align: center;
}}
.quote p {{
font-size: 1.2rem;
font-style: italic;
margin-bottom: 15px;
color: #2c3e50;
}}
.quote span {{
color: #667eea;
font-weight: 600;
}}
</style>
</head>
<body>
<div class="container">
<h1>{title}</h1>
{quote_items}
</div>
</body>
</html>
"""
@app.post("/display")
def display_quotes(quote_set: QuoteSet):
if not quote_set.quotes:
raise HTTPException(status_code=400, detail="No quotes provided")
quotes_dict = [{"text": q.text, "author": q.author} for q in quote_set.quotes]
html_content = quote_html(quote_set.title, quotes_dict)
headers = {"Content-Disposition": "inline"}
return HTMLResponse(content=html_content, headers=headers)
@app.get("/health")
def health_check():
return {"status": "healthy"}
@app.get("/")
def home():
return HTMLResponse(
content="""
<html>
<body style="font-family: system-ui; max-width: 600px; margin: 50px auto; padding: 20px;">
<h1>Quote Display Tool</h1>
<p>POST to /display with:</p>
<pre style="background: #f5f5f5; padding: 15px; border-radius: 5px;">{
"title": "My Quotes",
"quotes": [
{"text": "Hello world", "author": "Someone"}
]
}</pre>
</body>
</html>
"""
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
+7
View File
@@ -0,0 +1,7 @@
fastapi
uvicorn[standard]
pydantic
python-multipart
pytz
python-dateutil