feat: quotes-ui
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
|
pydantic
|
||||||
|
python-multipart
|
||||||
|
|
||||||
|
pytz
|
||||||
|
python-dateutil
|
||||||
Reference in New Issue
Block a user