Add README and server.py reference
This commit is contained in:
63
README.md
Normal file
63
README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Apple Form
|
||||
|
||||
Apple-style 3-field step-by-step customer form with FastAPI backend.
|
||||
|
||||
## Frontend
|
||||
|
||||
Single-file HTML (`index.html`) — zero dependencies, native system fonts.
|
||||
|
||||
- 3-step wizard: Email → Name → Domain
|
||||
- Apple design system: `#f5f5f7` backgrounds, Apple Blue `#0071e3` accent, SF Pro typography
|
||||
- Slide animations between steps, step-dot progress indicator
|
||||
- SVG checkmark draw animation on success
|
||||
- POSTs to the backend at `http://192.168.1.121:8080/api/submit`
|
||||
|
||||
### Open directly
|
||||
|
||||
```bash
|
||||
firefox index.html
|
||||
```
|
||||
|
||||
Or serve from an HTTP server:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
FastAPI server in a Proxmox LXC CT:
|
||||
|
||||
| Detail | Value |
|
||||
|---|---|
|
||||
| CT ID | 121 |
|
||||
| Hostname | form-login |
|
||||
| IP | 192.168.1.121 |
|
||||
| Port | 8080 |
|
||||
| Database | SQLite (`/srv/form-backend/submissions.db`) |
|
||||
| Service | `form-backend.service` (enabled, auto-start) |
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| POST | `/api/submit` | Submit form data `{email, name, domain}` → `{ok, id}` |
|
||||
| GET | `/api/submissions` | List all submissions (JSON) |
|
||||
| GET | `/health` | Health check |
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
curl http://192.168.1.121:8080/health
|
||||
curl -X POST http://192.168.1.121:8080/api/submit \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"jane@example.com","name":"Jane","domain":"example.io"}'
|
||||
```
|
||||
|
||||
### Server code
|
||||
|
||||
`server.py` lives at `/srv/form-backend/server.py` on the CT.
|
||||
|
||||
```bash
|
||||
ssh root@192.168.1.109 'pct exec 121 -- systemctl status form-backend'
|
||||
```
|
||||
75
server.py
Normal file
75
server.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import sqlite3
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
DB_PATH = os.environ.get("DB_PATH", "/srv/form-backend/submissions.db")
|
||||
|
||||
def get_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
return conn
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
db = get_db()
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS submissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
domain TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
db.commit()
|
||||
db.close()
|
||||
yield
|
||||
|
||||
app = FastAPI(title="Form Backend", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
class Submission(BaseModel):
|
||||
email: str
|
||||
name: str
|
||||
domain: str
|
||||
|
||||
@app.post("/api/submit", status_code=201)
|
||||
def submit(data: Submission):
|
||||
if not data.email or "@" not in data.email:
|
||||
return {"error": "Invalid email"}, 422
|
||||
if not data.name.strip():
|
||||
return {"error": "Name required"}, 422
|
||||
if not data.domain.strip():
|
||||
return {"error": "Domain required"}, 422
|
||||
|
||||
db = get_db()
|
||||
cur = db.execute(
|
||||
"INSERT INTO submissions (email, name, domain, created_at) VALUES (?, ?, ?, datetime(\"now\"))",
|
||||
(data.email.strip(), data.name.strip(), data.domain.strip())
|
||||
)
|
||||
db.commit()
|
||||
row_id = cur.lastrowid
|
||||
db.close()
|
||||
return {"ok": True, "id": row_id}
|
||||
|
||||
@app.get("/api/submissions")
|
||||
def list_submissions():
|
||||
db = get_db()
|
||||
rows = db.execute("SELECT id, email, name, domain, created_at FROM submissions ORDER BY id DESC LIMIT 100").fetchall()
|
||||
db.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
Reference in New Issue
Block a user