manage permission
This commit is contained in:
parent
ed004e99a4
commit
157ee4ef55
6 changed files with 72 additions and 16 deletions
|
@ -4,6 +4,5 @@ Simple API for food diary application.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] Add access restriction on each endpoint
|
|
||||||
- [ ] Add tests
|
- [ ] Add tests
|
||||||
- [ ] Add default servings
|
- [ ] Add default servings
|
||||||
|
|
|
@ -3,12 +3,17 @@ from fastapi import HTTPException
|
||||||
|
|
||||||
from ..model.entry import Entry, CreateEntryPayload, UpdateEntryPayload
|
from ..model.entry import Entry, CreateEntryPayload, UpdateEntryPayload
|
||||||
from ..domain.entry import Entry as DBEntry
|
from ..domain.entry import Entry as DBEntry
|
||||||
|
from ..domain.meal import Meal as DBMeal
|
||||||
from .base import AuthorizedController
|
from .base import AuthorizedController
|
||||||
|
|
||||||
|
|
||||||
class CreateEntry(AuthorizedController):
|
class CreateEntry(AuthorizedController):
|
||||||
async def call(self, content: CreateEntryPayload) -> Entry:
|
async def call(self, content: CreateEntryPayload) -> Entry:
|
||||||
async with self.async_session.begin() as session:
|
async with self.async_session.begin() as session:
|
||||||
|
meal = await DBMeal.get_by_id(session, self.user.id, content.meal_id)
|
||||||
|
if meal is None:
|
||||||
|
raise HTTPException(status_code=404, detail="meal not found")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
entry = await DBEntry.create(
|
entry = await DBEntry.create(
|
||||||
session, content.meal_id, content.product_id, content.grams
|
session, content.meal_id, content.product_id, content.grams
|
||||||
|
@ -21,7 +26,7 @@ class CreateEntry(AuthorizedController):
|
||||||
class UpdateEntry(AuthorizedController):
|
class UpdateEntry(AuthorizedController):
|
||||||
async def call(self, entry_id: int, content: UpdateEntryPayload) -> Entry:
|
async def call(self, entry_id: int, content: UpdateEntryPayload) -> Entry:
|
||||||
async with self.async_session.begin() as session:
|
async with self.async_session.begin() as session:
|
||||||
entry = await DBEntry.get_by_id(session, entry_id)
|
entry = await DBEntry.get_by_id(session, self.user.id, entry_id)
|
||||||
if entry is None:
|
if entry is None:
|
||||||
raise HTTPException(status_code=404, detail="entry not found")
|
raise HTTPException(status_code=404, detail="entry not found")
|
||||||
|
|
||||||
|
@ -37,7 +42,7 @@ class UpdateEntry(AuthorizedController):
|
||||||
class DeleteEntry(AuthorizedController):
|
class DeleteEntry(AuthorizedController):
|
||||||
async def call(self, entry_id: int) -> Entry:
|
async def call(self, entry_id: int) -> Entry:
|
||||||
async with self.async_session.begin() as session:
|
async with self.async_session.begin() as session:
|
||||||
entry = await DBEntry.get_by_id(session, entry_id)
|
entry = await DBEntry.get_by_id(session, self.user.id, entry_id)
|
||||||
if entry is None:
|
if entry is None:
|
||||||
raise HTTPException(status_code=404, detail="entry not found")
|
raise HTTPException(status_code=404, detail="entry not found")
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,18 @@ from fastapi import HTTPException
|
||||||
|
|
||||||
from ..model.meal import Meal, CreateMealPayload
|
from ..model.meal import Meal, CreateMealPayload
|
||||||
from ..domain.meal import Meal as DBMeal
|
from ..domain.meal import Meal as DBMeal
|
||||||
|
from ..domain.diary import Diary as DBDiary
|
||||||
from .base import AuthorizedController
|
from .base import AuthorizedController
|
||||||
|
|
||||||
|
|
||||||
class CreateMeal(AuthorizedController):
|
class CreateMeal(AuthorizedController):
|
||||||
async def call(self, content: CreateMealPayload) -> Meal:
|
async def call(self, content: CreateMealPayload) -> Meal:
|
||||||
async with self.async_session.begin() as session:
|
async with self.async_session.begin() as session:
|
||||||
|
if not await DBDiary.has_permission(
|
||||||
|
session, self.user.id, content.diary_id
|
||||||
|
):
|
||||||
|
raise HTTPException(status_code=404, detail="not found")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
meal = await DBMeal.create(
|
meal = await DBMeal.create(
|
||||||
session, content.diary_id, content.order, content.name
|
session, content.diary_id, content.order, content.name
|
||||||
|
|
|
@ -14,7 +14,9 @@ from .entry import Entry
|
||||||
class Diary(Base, CommonMixin):
|
class Diary(Base, CommonMixin):
|
||||||
"""Diary represents user diary for given day"""
|
"""Diary represents user diary for given day"""
|
||||||
|
|
||||||
meals: Mapped[list[Meal]] = relationship(lazy="selectin", order_by=Meal.order)
|
meals: Mapped[list[Meal]] = relationship(
|
||||||
|
lazy="selectin", order_by=Meal.order.desc()
|
||||||
|
)
|
||||||
date: Mapped[date] = mapped_column(Date)
|
date: Mapped[date] = mapped_column(Date)
|
||||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("user.id"))
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("user.id"))
|
||||||
|
|
||||||
|
@ -67,7 +69,7 @@ class Diary(Base, CommonMixin):
|
||||||
cls, session: AsyncSession, user_id: int, date: date
|
cls, session: AsyncSession, user_id: int, date: date
|
||||||
) -> "Optional[Diary]":
|
) -> "Optional[Diary]":
|
||||||
"""get_diary."""
|
"""get_diary."""
|
||||||
query = select(cls).where(cls.user_id == user_id).where(cls.date == date)
|
query = cls.query(user_id).where(cls.date == date)
|
||||||
return await session.scalar(query)
|
return await session.scalar(query)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -95,10 +97,12 @@ class Diary(Base, CommonMixin):
|
||||||
cls, session: AsyncSession, user_id: int, id: int
|
cls, session: AsyncSession, user_id: int, id: int
|
||||||
) -> "Optional[Diary]":
|
) -> "Optional[Diary]":
|
||||||
"""get_by_id."""
|
"""get_by_id."""
|
||||||
query = (
|
query = cls.query(user_id).where(cls.id == id)
|
||||||
select(cls)
|
|
||||||
.where(cls.user_id == user_id)
|
|
||||||
.where(cls.id == id)
|
|
||||||
.options(joinedload(cls.meals))
|
|
||||||
)
|
|
||||||
return await session.scalar(query)
|
return await session.scalar(query)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def has_permission(cls, session: AsyncSession, user_id: int, id: int) -> bool:
|
||||||
|
"""has_permission."""
|
||||||
|
query = select(cls.id).where(cls.user_id == user_id).where(cls.id == id)
|
||||||
|
obj = await session.scalar(query)
|
||||||
|
return obj is not None
|
||||||
|
|
|
@ -79,7 +79,7 @@ class Entry(Base, CommonMixin):
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
raise AssertionError("meal or product does not exist")
|
raise AssertionError("meal or product does not exist")
|
||||||
|
|
||||||
entry = await cls.get_by_id(session, entry.id)
|
entry = await cls._get_by_id(session, entry.id)
|
||||||
if not entry:
|
if not entry:
|
||||||
raise RuntimeError()
|
raise RuntimeError()
|
||||||
return entry
|
return entry
|
||||||
|
@ -111,11 +111,35 @@ class Entry(Base, CommonMixin):
|
||||||
raise AssertionError("product does not exist")
|
raise AssertionError("product does not exist")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_by_id(cls, session: AsyncSession, id: int) -> "Optional[Entry]":
|
async def _get_by_id(cls, session: AsyncSession, id: int) -> "Optional[Entry]":
|
||||||
"""get_by_id."""
|
"""get_by_id."""
|
||||||
query = select(cls).where(cls.id == id).options(joinedload(cls.product))
|
query = select(cls).where(cls.id == id).options(joinedload(cls.product))
|
||||||
return await session.scalar(query.order_by(cls.id))
|
return await session.scalar(query.order_by(cls.id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_id(
|
||||||
|
cls, session: AsyncSession, user_id: int, id: int
|
||||||
|
) -> "Optional[Entry]":
|
||||||
|
"""get_by_id."""
|
||||||
|
from .diary import Diary
|
||||||
|
from .meal import Meal
|
||||||
|
|
||||||
|
query = (
|
||||||
|
select(cls)
|
||||||
|
.where(cls.id == id)
|
||||||
|
.join(
|
||||||
|
Meal,
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
Diary,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
Diary.user_id == user_id,
|
||||||
|
)
|
||||||
|
.options(joinedload(cls.product))
|
||||||
|
)
|
||||||
|
return await session.scalar(query.order_by(cls.id))
|
||||||
|
|
||||||
async def delete(self, session) -> None:
|
async def delete(self, session) -> None:
|
||||||
"""delete."""
|
"""delete."""
|
||||||
await session.delete(self)
|
await session.delete(self)
|
||||||
|
|
|
@ -15,7 +15,9 @@ class Meal(Base, CommonMixin):
|
||||||
name: Mapped[str]
|
name: Mapped[str]
|
||||||
order: Mapped[int]
|
order: Mapped[int]
|
||||||
diary_id: Mapped[int] = mapped_column(Integer, ForeignKey("diary.id"))
|
diary_id: Mapped[int] = mapped_column(Integer, ForeignKey("diary.id"))
|
||||||
entries: Mapped[list[Entry]] = relationship(lazy="selectin", order_by=Entry.last_changed)
|
entries: Mapped[list[Entry]] = relationship(
|
||||||
|
lazy="selectin", order_by=Entry.last_changed
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def calories(self) -> float:
|
def calories(self) -> float:
|
||||||
|
@ -72,13 +74,29 @@ class Meal(Base, CommonMixin):
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
raise AssertionError("diary does not exist")
|
raise AssertionError("diary does not exist")
|
||||||
|
|
||||||
meal = await cls.get_by_id(session, meal.id)
|
meal = await cls._get_by_id(session, meal.id)
|
||||||
if not meal:
|
if not meal:
|
||||||
raise RuntimeError()
|
raise RuntimeError()
|
||||||
return meal
|
return meal
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_by_id(cls, session: AsyncSession, id: int) -> "Optional[Meal]":
|
async def _get_by_id(cls, session: AsyncSession, id: int) -> "Optional[Meal]":
|
||||||
"""get_by_id."""
|
"""get_by_id."""
|
||||||
query = select(cls).where(cls.id == id).options(joinedload(cls.entries))
|
query = select(cls).where(cls.id == id).options(joinedload(cls.entries))
|
||||||
return await session.scalar(query.order_by(cls.id))
|
return await session.scalar(query.order_by(cls.id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_id(
|
||||||
|
cls, session: AsyncSession, user_id: int, id: int
|
||||||
|
) -> "Optional[Meal]":
|
||||||
|
"""get_by_id."""
|
||||||
|
from .diary import Diary
|
||||||
|
|
||||||
|
query = (
|
||||||
|
select(cls)
|
||||||
|
.where(cls.id == id)
|
||||||
|
.join(Diary)
|
||||||
|
.where(Diary.user_id == user_id)
|
||||||
|
.options(joinedload(cls.entries))
|
||||||
|
)
|
||||||
|
return await session.scalar(query.order_by(cls.id))
|
||||||
|
|
Loading…
Reference in a new issue