|
| 1 | +from typing import List |
| 2 | +from fastapi import FastAPI, HTTPException |
| 3 | +from fastapi.responses import JSONResponse, StreamingResponse |
| 4 | +from pydantic import BaseModel, Field |
| 5 | +import time |
| 6 | +import uuid |
| 7 | +# import jwt |
| 8 | + |
| 9 | +SECRET_KEY = "CHANGE_ME_TO_SOMETHING_SECURE" |
| 10 | + |
| 11 | +app = FastAPI(title="GemStack Car‑Summon API (Mock)") |
| 12 | + |
| 13 | +### MODELS ### |
| 14 | + |
| 15 | +class LoginRequest(BaseModel): |
| 16 | + username: str |
| 17 | + password: str |
| 18 | + |
| 19 | +class CoordinatesRequest(BaseModel): |
| 20 | + lat: float = Field(..., ge=-90, le=90) |
| 21 | + lon: float = Field(..., ge=-180, le=180) |
| 22 | + |
| 23 | +class CoordinatesResponse(BaseModel): |
| 24 | + current_position: CoordinatesRequest |
| 25 | + optimized_route: List[CoordinatesRequest] |
| 26 | + eta: str |
| 27 | + |
| 28 | +class SummonResponse(BaseModel): |
| 29 | + launch_status: str |
| 30 | + launch_id: str |
| 31 | + |
| 32 | +class CancelRequest(BaseModel): |
| 33 | + launch_id: str |
| 34 | + |
| 35 | +class CancelResponse(BaseModel): |
| 36 | + launch_id: str |
| 37 | + status: str |
| 38 | + |
| 39 | +class StreamPosition(BaseModel): |
| 40 | + current_position: CoordinatesRequest |
| 41 | + launch_status: str |
| 42 | + eta: str |
| 43 | + |
| 44 | +### HELPERS ### |
| 45 | + |
| 46 | +# def create_jwt(username: str) -> str: |
| 47 | +# payload = {"sub": username, "jti": str(uuid.uuid4())} |
| 48 | +# return jwt.encode(payload, SECRET_KEY, algorithm="HS256") |
| 49 | + |
| 50 | +### ENDPOINTS ### |
| 51 | + |
| 52 | +# @app.post("/api/login") |
| 53 | +# def login(req: LoginRequest): |
| 54 | +# if req.username == "admin" and req.password == "password": |
| 55 | +# return {"token": create_jwt(req.username)} |
| 56 | +# raise HTTPException(status_code=401, detail="Invalid credentials") |
| 57 | + |
| 58 | +@app.post("/api/coordinates", response_model=CoordinatesResponse) |
| 59 | +def get_coordinates(req: CoordinatesRequest): |
| 60 | + # Mock “optimized route” as a straight line of 3 waypoints |
| 61 | + route = [ |
| 62 | + CoordinatesRequest(lat=req.lat + 0.001 * i, lon=req.lon + 0.001 * i) |
| 63 | + for i in range(1, 4) |
| 64 | + ] |
| 65 | + return CoordinatesResponse( |
| 66 | + current_position=CoordinatesRequest(lat=req.lat, lon=req.lon), |
| 67 | + optimized_route=route, |
| 68 | + eta="5 min", |
| 69 | + ) |
| 70 | + |
| 71 | +@app.post("/api/summon", response_model=SummonResponse) |
| 72 | +def summon(req: CoordinatesRequest): |
| 73 | + launch_id = str(uuid.uuid4()) |
| 74 | + return SummonResponse(launch_status="launched", launch_id=launch_id) |
| 75 | + |
| 76 | +@app.get("/api/stream_position/{launch_id}") |
| 77 | +def stream_position(launch_id: str): |
| 78 | + def event_generator(): |
| 79 | + lat, lon = 40.0930, -88.2350 |
| 80 | + for i in range(5): |
| 81 | + time.sleep(1) |
| 82 | + lat += 0.0005 |
| 83 | + lon += 0.0005 |
| 84 | + yield f"data: {StreamPosition(current_position=CoordinatesRequest(lat=lat, lon=lon), launch_status='navigating', eta=f'{5-i} min').json()}\n\n" |
| 85 | + yield "data: {\"launch_status\":\"arrived\"}\n\n" |
| 86 | + return StreamingResponse(event_generator(), media_type="text/event-stream") |
| 87 | + |
| 88 | +@app.post("/api/cancel", response_model=CancelResponse) |
| 89 | +def cancel(req: CancelRequest): |
| 90 | + return CancelResponse(launch_id=req.launch_id, status="cancelled") |
0 commit comments