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