diff --git a/main.py b/main.py index 1f02801..4a28064 100644 --- a/main.py +++ b/main.py @@ -7,18 +7,9 @@ from typing import List, Annotated, Optional, Union from fastapi import FastAPI, Depends, Query, Cookie from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder -from fastapi.middleware.cors import CORSMiddleware app = FastAPI() -# Add CORS middleware -app.add_middleware( - CORSMiddleware, - allow_origins=["*", "http://localhost:8080", "https://localhost:8080", "http://192.168.68.183:8080"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"] -) # Dependency to create SQLite connection async def get_db(): @@ -31,14 +22,14 @@ async def get_db(): async def cookie_person(user_id: Annotated[int, Cookie(alias='user_id')], conn: sqlite3.Connection = Depends(get_db)) -> persons.Person: return await persons.get_by_id(conn, user_id) -@app.get("/recipes/parse") +@app.get("/api/recipes/parse") async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db), person = Depends(cookie_person)) -> recipes.Recipe: parsed = await recipes.parse_recipe(conn, person, url) if not parsed: return JSONResponse(status_code=400, content={'message': 'Recipe not found'}) return parsed -@app.get("/recipes/ingredients/parse") +@app.get("/api/recipes/ingredients/parse") async def parse_ingredients(lines: Annotated[ List[str], Query(alias="ingredients", @@ -69,7 +60,7 @@ class ProductUrl(BaseModel): url: str tags: List[str] = [] -@app.post("/products/") +@app.post("/api/products") async def create_product(url: ProductUrl, conn: sqlite3.Connection = Depends(get_db)) -> products.Product: return await products.get_or_create(conn, url.url, url.tags) @@ -86,7 +77,7 @@ async def load_full_recipe(conn: sqlite3.Connection, id: int) -> recipes.Recipe: return r -@app.get("/recipes/") +@app.get("/api/recipes") async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(get_db)) -> List[recipes.Recipe]: result = [] if q: @@ -103,7 +94,7 @@ async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(g return result -@app.get("/recipes/{recipe_id}") +@app.get("/api/recipes/{recipe_id}") async def get_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe: r = await load_full_recipe(conn, recipe_id) if not r: @@ -146,7 +137,7 @@ async def delete_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_d await conn.commit() return recipe -@app.get("/meals/upcoming") +@app.get("/api/meals/upcoming") async def get_upcoming_meals(date_from: Annotated[datetime.datetime, Query(alias='from')], to: datetime.datetime, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Meal]: result = [] async for meal in meals.find_upcoming_meals_by_date_range(conn, date_from, to): @@ -157,7 +148,7 @@ async def get_upcoming_meals(date_from: Annotated[datetime.datetime, Query(alias return result -@app.get("/meals/{meal_id}") +@app.get("/api/meals/{meal_id}") async def get_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: meal = await meals.find_meal_by_id(conn, meal_id) if not meal: @@ -200,8 +191,8 @@ def validate_meal(meal : meals.Meal) -> JSONResponse | None: return JSONResponse(status_code=400, content={'message': f'Duplicate consumer: {", ".join(duplicates)}'}) return None - -@app.post("/meals/") + +@app.post("/api/meals") async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: validation_response = validate_meal(meal) if validation_response: @@ -211,7 +202,7 @@ async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_d await conn.commit() return meal -@app.put("/meals/{meal_id}") +@app.put("/api/meals/{meal_id}") async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: if meal.id != meal_id: return JSONResponse(status_code=400, content={'message': 'Meal ID in URL does not match meal ID in body'}) @@ -229,7 +220,7 @@ async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection = return await get_meal(meal_id, conn) -@app.post("/meals/{meal_id}/consumed") +@app.post("/api/meals/{meal_id}/consumed") async def mark_consumed(meal_id: int, consumed_date: Optional[datetime.datetime] = None, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> meals.Meal: meal = await meals.find_meal_by_id(conn, meal_id) if not meal: @@ -239,7 +230,7 @@ async def mark_consumed(meal_id: int, consumed_date: Optional[datetime.datetime] await conn.commit() return meal -@app.delete("/meals/{meal_id}") +@app.delete("/api/meals/{meal_id}") async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: meal = await meals.find_meal_by_id(conn, meal_id) if not meal: @@ -249,7 +240,7 @@ async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) await conn.commit() return meal -@app.get("/shopping/{list_id}") +@app.get("/api/shopping/{list_id}") async def get_shopping_list(list_id: Union[int, str], conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList: if list_id.lower() == 'current': return await shopping.current_shopping_list(conn) @@ -261,7 +252,7 @@ async def get_shopping_list(list_id: Union[int, str], conn: sqlite3.Connection = return await shopping.load_shopping_list(conn, list_id) -@app.post("/shopping/current/purchased") +@app.post("/api/shopping/current/purchased") async def mark_purchased(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingList: response = await shopping.mark_purchased(conn) @@ -274,7 +265,7 @@ class FoundResult(BaseModel): created: List[shopping.ShoppingListResult] = [] removed: List[shopping.ShoppingListResult] = [] -@app.post("/shopping/current/found") +@app.post("/api/shopping/current/found") async def mark_shopping_list(ingredients: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db)) -> FoundResult: now = datetime.datetime.now() result = FoundResult() @@ -288,18 +279,18 @@ async def mark_shopping_list(ingredients: List[ingredients.Ingredient], conn: sq await conn.commit() return result -@app.delete("/shopping/current/found/{product_id}") +@app.delete("/api/shopping/current/found/{product_id}") async def unmark_shopping_list(product_id: int, conn: sqlite3.Connection = Depends(get_db)) -> List[shopping.ShoppingListResult]: response = await shopping.unmark_found(conn, product_id) await conn.commit() return response -@app.get("/shopping/current/me/ingredients") +@app.get("/api/shopping/current/me/ingredients") async def get_my_shopping_list(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> List[shopping.ShoppingListRequest]: current = await shopping.current_shopping_list(conn) return [r async for r in shopping.get_persons_requests(conn, current, person) if r.ingredient] -@app.post("/shopping/current/me/ingredients") +@app.post("/api/shopping/current/me/ingredients") async def sync_my_shopping_list(requests: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> List[shopping.ShoppingListRequest]: current = await shopping.current_shopping_list(conn) result = [r async for r in shopping.sync_persons_requested_ingredients(conn, current, person, requests) if r.ingredient] @@ -309,7 +300,7 @@ async def sync_my_shopping_list(requests: List[ingredients.Ingredient], conn: sq class MealIdWrapper(BaseModel): meal_id: int -@app.post("/shopping/current/meals/me") +@app.post("/api/shopping/current/meals/me") async def request_meal(r: MealIdWrapper, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingListRequest: current = await shopping.current_shopping_list(conn) meal = await meals.find_meal_by_id(conn, r.meal_id) @@ -320,7 +311,7 @@ async def request_meal(r: MealIdWrapper, conn: sqlite3.Connection = Depends(get_ await conn.commit() return response -@app.delete("/shopping/current/meals/{meal_id}") +@app.delete("/api/shopping/current/meals/{meal_id}") async def unrequest_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> dict: current = await shopping.current_shopping_list(conn) meal = await meals.find_meal_by_id(conn, meal_id) @@ -331,7 +322,7 @@ async def unrequest_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db await conn.commit() return {} -@app.get("/persons/") +@app.get("/api/persons") async def get_persons(q: str = None, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Person]: query = persons.search_by_name(conn, q) if q else persons.get_all(conn) result = [] @@ -340,7 +331,7 @@ async def get_persons(q: str = None, conn: sqlite3.Connection = Depends(get_db)) return result -@app.post("/persons/") +@app.post("/api/persons") async def create_person(person: persons.Person, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person: await persons.insert_person(conn, person) await conn.commit() @@ -349,7 +340,7 @@ async def create_person(person: persons.Person, conn: sqlite3.Connection = Depen class LoginBody(BaseModel): username: str -@app.post('/auth/login') +@app.post('/api/auth/login') async def login(data: LoginBody, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person: person = await persons.get_by_name(conn, data.username) if not person: @@ -359,6 +350,37 @@ async def login(data: LoginBody, conn: sqlite3.Connection = Depends(get_db)) -> response.set_cookie(key='user_id', value=str(person.id)) return response -@app.post('/auth/refresh') +@app.post('/api/auth/refresh') async def current_user(user: persons.Person = Depends(cookie_person)) -> persons.Person: - return user \ No newline at end of file + return user + +import os + +if os.environ.get('DOOF_PROD', False): + from fastapi.staticfiles import StaticFiles + app.mount("/", StaticFiles(directory="./front-dist", html=True), name="front-dist") +else: + # Proxy the request to the frontend development server + from starlette.requests import Request + from starlette.responses import StreamingResponse + from starlette.background import BackgroundTask + + import httpx + + client = httpx.AsyncClient(base_url="http://localhost:8080/") + + async def _reverse_proxy(request: Request): + url = httpx.URL(path=request.url.path, + query=request.url.query.encode("utf-8")) + rp_req = client.build_request(request.method, url, + headers=request.headers.raw, + content=request.stream()) + rp_resp = await client.send(rp_req, stream=True) + return StreamingResponse( + rp_resp.aiter_raw(), + status_code=rp_resp.status_code, + headers=rp_resp.headers, + background=BackgroundTask(rp_resp.aclose), + ) + + app.add_route("/{path:path}",_reverse_proxy, ["GET", "POST"]) \ No newline at end of file