Add README and server.py reference

This commit is contained in:
2026-06-13 10:04:52 +01:00
parent e48d80b192
commit 9d5d05725c
2 changed files with 138 additions and 0 deletions

63
README.md Normal file
View 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
View 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"}