Compare commits
No commits in common. "b1905cc03a10c71ea7b73ff3e1036da773524981" and "407d649d993858e031b4a6aa2196832e6b8ed80f" have entirely different histories.
b1905cc03a
...
407d649d99
2 changed files with 44 additions and 62 deletions
90
main.py
90
main.py
|
|
@ -7,9 +7,18 @@ from typing import List, Annotated, Optional, Union
|
||||||
from fastapi import FastAPI, Depends, Query, Cookie
|
from fastapi import FastAPI, Depends, Query, Cookie
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
app = FastAPI()
|
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
|
# Dependency to create SQLite connection
|
||||||
async def get_db():
|
async def get_db():
|
||||||
|
|
@ -22,14 +31,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:
|
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)
|
return await persons.get_by_id(conn, user_id)
|
||||||
|
|
||||||
@app.get("/api/recipes/parse")
|
@app.get("/recipes/parse")
|
||||||
async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db), person = Depends(cookie_person)) -> recipes.Recipe:
|
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)
|
parsed = await recipes.parse_recipe(conn, person, url)
|
||||||
if not parsed:
|
if not parsed:
|
||||||
return JSONResponse(status_code=400, content={'message': 'Recipe not found'})
|
return JSONResponse(status_code=400, content={'message': 'Recipe not found'})
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
@app.get("/api/recipes/ingredients/parse")
|
@app.get("/recipes/ingredients/parse")
|
||||||
async def parse_ingredients(lines: Annotated[
|
async def parse_ingredients(lines: Annotated[
|
||||||
List[str],
|
List[str],
|
||||||
Query(alias="ingredients",
|
Query(alias="ingredients",
|
||||||
|
|
@ -60,7 +69,7 @@ class ProductUrl(BaseModel):
|
||||||
url: str
|
url: str
|
||||||
tags: List[str] = []
|
tags: List[str] = []
|
||||||
|
|
||||||
@app.post("/api/products")
|
@app.post("/products/")
|
||||||
async def create_product(url: ProductUrl, conn: sqlite3.Connection = Depends(get_db)) -> products.Product:
|
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)
|
return await products.get_or_create(conn, url.url, url.tags)
|
||||||
|
|
||||||
|
|
@ -77,7 +86,7 @@ async def load_full_recipe(conn: sqlite3.Connection, id: int) -> recipes.Recipe:
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@app.get("/api/recipes")
|
@app.get("/recipes/")
|
||||||
async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(get_db)) -> List[recipes.Recipe]:
|
async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(get_db)) -> List[recipes.Recipe]:
|
||||||
result = []
|
result = []
|
||||||
if q:
|
if q:
|
||||||
|
|
@ -94,7 +103,7 @@ async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(g
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@app.get("/api/recipes/{recipe_id}")
|
@app.get("/recipes/{recipe_id}")
|
||||||
async def get_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
|
async def get_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
|
||||||
r = await load_full_recipe(conn, recipe_id)
|
r = await load_full_recipe(conn, recipe_id)
|
||||||
if not r:
|
if not r:
|
||||||
|
|
@ -137,7 +146,7 @@ async def delete_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_d
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
@app.get("/api/meals/upcoming")
|
@app.get("/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]:
|
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 = []
|
result = []
|
||||||
async for meal in meals.find_upcoming_meals_by_date_range(conn, date_from, to):
|
async for meal in meals.find_upcoming_meals_by_date_range(conn, date_from, to):
|
||||||
|
|
@ -148,7 +157,7 @@ async def get_upcoming_meals(date_from: Annotated[datetime.datetime, Query(alias
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@app.get("/api/meals/{meal_id}")
|
@app.get("/meals/{meal_id}")
|
||||||
async def get_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
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)
|
meal = await meals.find_meal_by_id(conn, meal_id)
|
||||||
if not meal:
|
if not meal:
|
||||||
|
|
@ -191,8 +200,8 @@ def validate_meal(meal : meals.Meal) -> JSONResponse | None:
|
||||||
return JSONResponse(status_code=400, content={'message': f'Duplicate consumer: {", ".join(duplicates)}'})
|
return JSONResponse(status_code=400, content={'message': f'Duplicate consumer: {", ".join(duplicates)}'})
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@app.post("/api/meals")
|
@app.post("/meals/")
|
||||||
async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
||||||
validation_response = validate_meal(meal)
|
validation_response = validate_meal(meal)
|
||||||
if validation_response:
|
if validation_response:
|
||||||
|
|
@ -202,7 +211,7 @@ async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_d
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return meal
|
return meal
|
||||||
|
|
||||||
@app.put("/api/meals/{meal_id}")
|
@app.put("/meals/{meal_id}")
|
||||||
async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
||||||
if meal.id != meal_id:
|
if meal.id != meal_id:
|
||||||
return JSONResponse(status_code=400, content={'message': 'Meal ID in URL does not match meal ID in body'})
|
return JSONResponse(status_code=400, content={'message': 'Meal ID in URL does not match meal ID in body'})
|
||||||
|
|
@ -220,7 +229,7 @@ async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection =
|
||||||
|
|
||||||
return await get_meal(meal_id, conn)
|
return await get_meal(meal_id, conn)
|
||||||
|
|
||||||
@app.post("/api/meals/{meal_id}/consumed")
|
@app.post("/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:
|
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)
|
meal = await meals.find_meal_by_id(conn, meal_id)
|
||||||
if not meal:
|
if not meal:
|
||||||
|
|
@ -230,7 +239,7 @@ async def mark_consumed(meal_id: int, consumed_date: Optional[datetime.datetime]
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return meal
|
return meal
|
||||||
|
|
||||||
@app.delete("/api/meals/{meal_id}")
|
@app.delete("/meals/{meal_id}")
|
||||||
async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
|
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)
|
meal = await meals.find_meal_by_id(conn, meal_id)
|
||||||
if not meal:
|
if not meal:
|
||||||
|
|
@ -240,7 +249,7 @@ async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db))
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return meal
|
return meal
|
||||||
|
|
||||||
@app.get("/api/shopping/{list_id}")
|
@app.get("/shopping/{list_id}")
|
||||||
async def get_shopping_list(list_id: Union[int, str], conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
async def get_shopping_list(list_id: Union[int, str], conn: sqlite3.Connection = Depends(get_db)) -> shopping.ShoppingList:
|
||||||
if list_id.lower() == 'current':
|
if list_id.lower() == 'current':
|
||||||
return await shopping.current_shopping_list(conn)
|
return await shopping.current_shopping_list(conn)
|
||||||
|
|
@ -252,7 +261,7 @@ async def get_shopping_list(list_id: Union[int, str], conn: sqlite3.Connection =
|
||||||
|
|
||||||
return await shopping.load_shopping_list(conn, list_id)
|
return await shopping.load_shopping_list(conn, list_id)
|
||||||
|
|
||||||
@app.post("/api/shopping/current/purchased")
|
@app.post("/shopping/current/purchased")
|
||||||
async def mark_purchased(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingList:
|
async def mark_purchased(conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingList:
|
||||||
response = await shopping.mark_purchased(conn)
|
response = await shopping.mark_purchased(conn)
|
||||||
|
|
||||||
|
|
@ -265,7 +274,7 @@ class FoundResult(BaseModel):
|
||||||
created: List[shopping.ShoppingListResult] = []
|
created: List[shopping.ShoppingListResult] = []
|
||||||
removed: List[shopping.ShoppingListResult] = []
|
removed: List[shopping.ShoppingListResult] = []
|
||||||
|
|
||||||
@app.post("/api/shopping/current/found")
|
@app.post("/shopping/current/found")
|
||||||
async def mark_shopping_list(ingredients: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db)) -> FoundResult:
|
async def mark_shopping_list(ingredients: List[ingredients.Ingredient], conn: sqlite3.Connection = Depends(get_db)) -> FoundResult:
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
result = FoundResult()
|
result = FoundResult()
|
||||||
|
|
@ -279,18 +288,18 @@ async def mark_shopping_list(ingredients: List[ingredients.Ingredient], conn: sq
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@app.delete("/api/shopping/current/found/{product_id}")
|
@app.delete("/shopping/current/found/{product_id}")
|
||||||
async def unmark_shopping_list(product_id: int, conn: sqlite3.Connection = Depends(get_db)) -> List[shopping.ShoppingListResult]:
|
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)
|
response = await shopping.unmark_found(conn, product_id)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.get("/api/shopping/current/me/ingredients")
|
@app.get("/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]:
|
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)
|
current = await shopping.current_shopping_list(conn)
|
||||||
return [r async for r in shopping.get_persons_requests(conn, current, person) if r.ingredient]
|
return [r async for r in shopping.get_persons_requests(conn, current, person) if r.ingredient]
|
||||||
|
|
||||||
@app.post("/api/shopping/current/me/ingredients")
|
@app.post("/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]:
|
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)
|
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]
|
result = [r async for r in shopping.sync_persons_requested_ingredients(conn, current, person, requests) if r.ingredient]
|
||||||
|
|
@ -300,7 +309,7 @@ async def sync_my_shopping_list(requests: List[ingredients.Ingredient], conn: sq
|
||||||
class MealIdWrapper(BaseModel):
|
class MealIdWrapper(BaseModel):
|
||||||
meal_id: int
|
meal_id: int
|
||||||
|
|
||||||
@app.post("/api/shopping/current/meals/me")
|
@app.post("/shopping/current/meals/me")
|
||||||
async def request_meal(r: MealIdWrapper, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> shopping.ShoppingListRequest:
|
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)
|
current = await shopping.current_shopping_list(conn)
|
||||||
meal = await meals.find_meal_by_id(conn, r.meal_id)
|
meal = await meals.find_meal_by_id(conn, r.meal_id)
|
||||||
|
|
@ -311,7 +320,7 @@ async def request_meal(r: MealIdWrapper, conn: sqlite3.Connection = Depends(get_
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.delete("/api/shopping/current/meals/{meal_id}")
|
@app.delete("/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:
|
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)
|
current = await shopping.current_shopping_list(conn)
|
||||||
meal = await meals.find_meal_by_id(conn, meal_id)
|
meal = await meals.find_meal_by_id(conn, meal_id)
|
||||||
|
|
@ -322,7 +331,7 @@ async def unrequest_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@app.get("/api/persons")
|
@app.get("/persons/")
|
||||||
async def get_persons(q: str = None, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Person]:
|
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)
|
query = persons.search_by_name(conn, q) if q else persons.get_all(conn)
|
||||||
result = []
|
result = []
|
||||||
|
|
@ -331,7 +340,7 @@ async def get_persons(q: str = None, conn: sqlite3.Connection = Depends(get_db))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@app.post("/api/persons")
|
@app.post("/persons/")
|
||||||
async def create_person(person: persons.Person, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
|
async def create_person(person: persons.Person, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
|
||||||
await persons.insert_person(conn, person)
|
await persons.insert_person(conn, person)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
|
|
@ -340,7 +349,7 @@ async def create_person(person: persons.Person, conn: sqlite3.Connection = Depen
|
||||||
class LoginBody(BaseModel):
|
class LoginBody(BaseModel):
|
||||||
username: str
|
username: str
|
||||||
|
|
||||||
@app.post('/api/auth/login')
|
@app.post('/auth/login')
|
||||||
async def login(data: LoginBody, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
|
async def login(data: LoginBody, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
|
||||||
person = await persons.get_by_name(conn, data.username)
|
person = await persons.get_by_name(conn, data.username)
|
||||||
if not person:
|
if not person:
|
||||||
|
|
@ -350,37 +359,6 @@ async def login(data: LoginBody, conn: sqlite3.Connection = Depends(get_db)) ->
|
||||||
response.set_cookie(key='user_id', value=str(person.id))
|
response.set_cookie(key='user_id', value=str(person.id))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.post('/api/auth/refresh')
|
@app.post('/auth/refresh')
|
||||||
async def current_user(user: persons.Person = Depends(cookie_person)) -> persons.Person:
|
async def current_user(user: persons.Person = Depends(cookie_person)) -> persons.Person:
|
||||||
return user
|
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"])
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
fastapi==0.115.0
|
annotated-types==0.6.0
|
||||||
pydantic==2.9.2
|
anyio==4.2.0
|
||||||
httpx==0.27.2
|
exceptiongroup==1.2.0
|
||||||
ingredient-parser-nlp==1.1.2
|
fastapi==0.108.0
|
||||||
beautifulsoup4==4.12.3
|
idna==3.6
|
||||||
aiosqlite==0.20.0
|
pydantic==2.5.3
|
||||||
|
pydantic_core==2.14.6
|
||||||
|
sniffio==1.3.0
|
||||||
|
starlette==0.32.0.post1
|
||||||
|
typing_extensions==4.9.0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue