[user settings] endpionts
This commit is contained in:
parent
13fd45e72e
commit
0ea4c0b4b0
9 changed files with 176 additions and 1 deletions
19
fooder/controller/user_settings.py
Normal file
19
fooder/controller/user_settings.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from fooder.controller.base import ModelController
|
||||
from fooder.domain import UserSettings
|
||||
from fooder.model.user_settings import UserSettingsUpdateModel
|
||||
|
||||
|
||||
class UserSettingsController(ModelController[UserSettings]):
|
||||
async def update(self, data: UserSettingsUpdateModel) -> None:
|
||||
if data.protein_goal is not None:
|
||||
self.obj.protein_goal = data.protein_goal
|
||||
if data.carb_goal is not None:
|
||||
self.obj.carb_goal = data.carb_goal
|
||||
if data.fat_goal is not None:
|
||||
self.obj.fat_goal = data.fat_goal
|
||||
if data.fiber_goal is not None:
|
||||
self.obj.fiber_goal = data.fiber_goal
|
||||
if data.calories_goal is not None:
|
||||
self.obj.calories_goal = data.calories_goal
|
||||
|
||||
await self.ctx.repo.user_settings.update(self.obj)
|
||||
18
fooder/model/user_settings.py
Normal file
18
fooder/model/user_settings.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from .base import ObjModelMixin, Calories, OptionalCalories
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class UserSettingsModel(ObjModelMixin, BaseModel):
|
||||
protein_goal: Calories
|
||||
carb_goal: Calories
|
||||
fat_goal: Calories
|
||||
fiber_goal: Calories
|
||||
calories_goal: Calories
|
||||
|
||||
|
||||
class UserSettingsUpdateModel(BaseModel):
|
||||
protein_goal: OptionalCalories = None
|
||||
carb_goal: OptionalCalories = None
|
||||
fat_goal: OptionalCalories = None
|
||||
fiber_goal: OptionalCalories = None
|
||||
calories_goal: OptionalCalories = None
|
||||
|
|
@ -6,7 +6,8 @@ from sqlalchemy.exc import IntegrityError
|
|||
from fooder.repository.user import UserRepository
|
||||
from fooder.repository.product import ProductRepository
|
||||
from fooder.repository.user_product_usage import UserProductUsageRepository
|
||||
from fooder.domain import User, Product, UserProductUsage
|
||||
from fooder.repository.user_settings import UserSettingsRepository
|
||||
from fooder.domain import User, Product, UserProductUsage, UserSettings
|
||||
from fooder.exc import Conflict
|
||||
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ class Repository:
|
|||
self.user = UserRepository(User, session)
|
||||
self.product = ProductRepository(Product, session)
|
||||
self.user_product_usage = UserProductUsageRepository(UserProductUsage, session)
|
||||
self.user_settings = UserSettingsRepository(UserSettings, session)
|
||||
|
||||
async def commit(self) -> None:
|
||||
try:
|
||||
|
|
|
|||
7
fooder/repository/user_settings.py
Normal file
7
fooder/repository/user_settings.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from fooder.domain import UserSettings
|
||||
from .base import RepositoryBase
|
||||
|
||||
|
||||
class UserSettingsRepository(RepositoryBase[UserSettings]):
|
||||
async def get_by_user_id(self, user_id: int) -> UserSettings:
|
||||
return await self._get(UserSettings.user_id == user_id)
|
||||
|
|
@ -2,7 +2,11 @@ from fastapi import APIRouter
|
|||
|
||||
from fooder.view.token import router as token_router
|
||||
from fooder.view.product import router as product_router
|
||||
from fooder.view.user_settings import router as user_settings_router
|
||||
|
||||
router = APIRouter(prefix="/api")
|
||||
router.include_router(token_router, prefix="/token", tags=["token"])
|
||||
router.include_router(product_router, prefix="/product", tags=["product"])
|
||||
router.include_router(
|
||||
user_settings_router, prefix="/user/settings", tags=["user_settings"]
|
||||
)
|
||||
|
|
|
|||
1
fooder/test/fixtures/__init__.py
vendored
1
fooder/test/fixtures/__init__.py
vendored
|
|
@ -5,6 +5,7 @@ from .user import *
|
|||
from .client import *
|
||||
from .context import *
|
||||
from .product import *
|
||||
from .user_settings import *
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
17
fooder/test/fixtures/user_settings.py
vendored
Normal file
17
fooder/test/fixtures/user_settings.py
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import pytest_asyncio
|
||||
|
||||
from fooder.domain.user_settings import UserSettings
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def user_settings(ctx, user):
|
||||
settings = UserSettings(
|
||||
user_id=user.id,
|
||||
protein_goal=150.0,
|
||||
carb_goal=200.0,
|
||||
fat_goal=70.0,
|
||||
fiber_goal=30.0,
|
||||
calories_goal=2000.0,
|
||||
)
|
||||
await ctx.repo.user_settings.create(settings)
|
||||
return settings
|
||||
80
fooder/test/view/test_user_settings.py
Normal file
80
fooder/test/view/test_user_settings.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
async def test_get_user_settings_returns_200(auth_client, user_settings):
|
||||
response = await auth_client.get("/api/user/settings")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
async def test_get_user_settings_returns_correct_fields(auth_client, user_settings):
|
||||
response = await auth_client.get("/api/user/settings")
|
||||
body = response.json()
|
||||
assert body["protein_goal"] == user_settings.protein_goal
|
||||
assert body["carb_goal"] == user_settings.carb_goal
|
||||
assert body["fat_goal"] == user_settings.fat_goal
|
||||
assert body["fiber_goal"] == user_settings.fiber_goal
|
||||
assert body["calories_goal"] == user_settings.calories_goal
|
||||
assert body["id"] == user_settings.id
|
||||
|
||||
|
||||
async def test_get_user_settings_not_found_returns_404(auth_client):
|
||||
response = await auth_client.get("/api/user/settings")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
async def test_get_user_settings_without_auth_returns_401(client):
|
||||
response = await client.get("/api/user/settings")
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
async def test_update_user_settings_returns_200(auth_client, user_settings):
|
||||
response = await auth_client.patch(
|
||||
"/api/user/settings", json={"protein_goal": 180.0}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
async def test_update_user_settings_partial_update(auth_client, user_settings):
|
||||
response = await auth_client.patch(
|
||||
"/api/user/settings", json={"protein_goal": 180.0}
|
||||
)
|
||||
body = response.json()
|
||||
assert body["protein_goal"] == 180.0
|
||||
assert body["carb_goal"] == user_settings.carb_goal
|
||||
assert body["fat_goal"] == user_settings.fat_goal
|
||||
assert body["calories_goal"] == user_settings.calories_goal
|
||||
|
||||
|
||||
async def test_update_user_settings_all_fields(auth_client, user_settings):
|
||||
payload = {
|
||||
"protein_goal": 120.0,
|
||||
"carb_goal": 250.0,
|
||||
"fat_goal": 60.0,
|
||||
"fiber_goal": 25.0,
|
||||
"calories_goal": 1800.0,
|
||||
}
|
||||
response = await auth_client.patch("/api/user/settings", json=payload)
|
||||
body = response.json()
|
||||
assert body["protein_goal"] == 120.0
|
||||
assert body["carb_goal"] == 250.0
|
||||
assert body["fat_goal"] == 60.0
|
||||
assert body["fiber_goal"] == 25.0
|
||||
assert body["calories_goal"] == 1800.0
|
||||
|
||||
|
||||
async def test_update_user_settings_not_found_returns_404(auth_client):
|
||||
response = await auth_client.patch(
|
||||
"/api/user/settings", json={"protein_goal": 180.0}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
async def test_update_user_settings_negative_value_returns_422(
|
||||
auth_client, user_settings
|
||||
):
|
||||
response = await auth_client.patch(
|
||||
"/api/user/settings", json={"protein_goal": -10.0}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
async def test_update_user_settings_without_auth_returns_401(client):
|
||||
response = await client.patch("/api/user/settings", json={"protein_goal": 180.0})
|
||||
assert response.status_code == 401
|
||||
27
fooder/view/user_settings.py
Normal file
27
fooder/view/user_settings.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
|
||||
from fooder.context import Context, AuthContextDependency
|
||||
from fooder.controller.user_settings import UserSettingsController
|
||||
from fooder.model.user_settings import UserSettingsModel, UserSettingsUpdateModel
|
||||
|
||||
router = APIRouter(tags=["user_settings"])
|
||||
|
||||
_auth_ctx = AuthContextDependency()
|
||||
|
||||
|
||||
@router.get("", response_model=UserSettingsModel)
|
||||
async def get_user_settings(
|
||||
ctx: Context = Depends(_auth_ctx),
|
||||
):
|
||||
return await ctx.repo.user_settings.get_by_user_id(ctx.user.id)
|
||||
|
||||
|
||||
@router.patch("", response_model=UserSettingsModel)
|
||||
async def update_user_settings(
|
||||
data: UserSettingsUpdateModel,
|
||||
ctx: Context = Depends(_auth_ctx),
|
||||
):
|
||||
async with ctx.repo.transaction():
|
||||
settings = await ctx.repo.user_settings.get_by_user_id(ctx.user.id)
|
||||
await UserSettingsController(ctx, settings).update(data)
|
||||
return settings
|
||||
Loading…
Reference in a new issue