-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
aafc200
commit 2d894a3
Showing
40 changed files
with
805 additions
and
392 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import logging | ||
from collections.abc import Sequence | ||
|
||
from fastapi import APIRouter | ||
from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT | ||
|
||
from core.dependencies import FirebaseUserDep, SessionDep | ||
from core.exceptions import NotFoundException | ||
from crud.account import create_account, find_accounts | ||
from crud.user import get_or_create_user | ||
from models import Account, AccountCreate, AccountSerializer, AccountUpdate | ||
|
||
router = APIRouter() | ||
logger = logging.getLogger("budgly") | ||
|
||
|
||
@router.get("/", response_model=list[AccountSerializer]) | ||
async def list_accounts( | ||
user: FirebaseUserDep, session: SessionDep | ||
) -> Sequence[Account]: | ||
results = await find_accounts(session, user.uid) | ||
return results.all() | ||
|
||
|
||
@router.post("/", response_model=AccountSerializer, status_code=HTTP_201_CREATED) | ||
async def create( | ||
payload: AccountCreate, user: FirebaseUserDep, session: SessionDep | ||
) -> Account: | ||
user = await get_or_create_user(session, user) | ||
account = await create_account(session, payload, user) | ||
return account | ||
|
||
|
||
@router.patch("/{account_id}", response_model=AccountSerializer) | ||
async def update( | ||
account_id: int, payload: AccountUpdate, user: FirebaseUserDep, session: SessionDep | ||
) -> Account: | ||
results = await find_accounts(session, user.uid, account_id) | ||
account = results.one_or_none() | ||
if account is None: | ||
raise NotFoundException(f"Account(id: {account_id}) not found") | ||
|
||
for key, value in payload.model_dump(exclude_unset=True).items(): | ||
setattr(account, key, value) | ||
|
||
session.add(account) | ||
await session.commit() | ||
await session.refresh(account) | ||
|
||
return account | ||
|
||
|
||
@router.delete("/{account_id}", status_code=HTTP_204_NO_CONTENT) | ||
async def delete_accounts( | ||
account_id: int, user: FirebaseUserDep, session: SessionDep | ||
) -> None: | ||
results = await find_accounts(session, user.uid, account_id) | ||
account = results.one_or_none() | ||
if account is None: | ||
raise NotFoundException(f"Account(id: {account_id}) not found") | ||
|
||
await session.delete(account) | ||
await session.commit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import logging | ||
|
||
from sqlalchemy import ScalarResult | ||
from sqlalchemy.exc import IntegrityError | ||
from sqlmodel import select | ||
from sqlmodel.ext.asyncio.session import AsyncSession | ||
|
||
from core.exceptions import ValidationException | ||
from models import Account, AccountCreate, User | ||
|
||
logger = logging.getLogger("budgly") | ||
|
||
|
||
async def find_accounts( | ||
session: AsyncSession, user_uid: str, account_id: int | None = None | ||
) -> ScalarResult[Account]: | ||
query = select(Account).where(Account.users.any(User.uid == user_uid)) # type: ignore | ||
if account_id is not None: | ||
query = query.where(Account.id == account_id) | ||
result = await session.exec(query) | ||
return result | ||
|
||
|
||
async def create_account( | ||
session: AsyncSession, payload: AccountCreate, user: User | ||
) -> Account: | ||
try: | ||
account = Account(**payload.model_dump(mode="json"), creator=user) | ||
account.users.append(user) | ||
session.add(account) | ||
await session.commit() | ||
await session.refresh(account) | ||
return account | ||
except IntegrityError as e: | ||
logger.error(e) | ||
await session.rollback() | ||
if "UniqueViolationError" in str(e): | ||
raise ValidationException(f"Account name ({payload.name}) already exists") | ||
raise ValidationException("Failed to create account") | ||
except Exception as e: | ||
logger.error(e) | ||
raise ValidationException("Failed to validate account model") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import logging | ||
import random | ||
|
||
from pydantic import BaseModel, HttpUrl, field_serializer | ||
from sqlmodel import Field, Relationship, String, UniqueConstraint | ||
|
||
from core.db import BaseTable | ||
from models.extra import UserAccountLink | ||
from models.user import User | ||
|
||
logger = logging.getLogger("budgly") | ||
|
||
|
||
def random_color_hex_code() -> str: | ||
def hex() -> int: | ||
return random.randint(0, 255) | ||
|
||
return f"#{hex():02X}{hex():02X}{hex():02X}" | ||
|
||
|
||
class AccountCreate(BaseModel): | ||
name: str | ||
image: HttpUrl | None = None | ||
color: str | None = None | ||
|
||
@field_serializer("color") | ||
def serialize_color(self, value: str | None) -> str | None: | ||
if value is None and self.image is None: | ||
return random_color_hex_code() | ||
|
||
return value | ||
|
||
|
||
class AccountUpdate(BaseModel): | ||
name: str | None = None | ||
image: HttpUrl | None = None | ||
color: str | None = None | ||
|
||
@field_serializer("color") | ||
def serialize_color(self, value: str | None) -> str | None: | ||
if value is None and "image" in self.model_fields_set and self.image is None: | ||
return random_color_hex_code() | ||
|
||
return value | ||
|
||
|
||
class Account(BaseTable, table=True): | ||
name: str | ||
image: HttpUrl | None = Field(sa_type=String) | ||
color: str | None | ||
creator_id: str = Field(foreign_key="user.uid") | ||
|
||
creator: User = Relationship(back_populates="created_accounts") | ||
users: list[User] = Relationship( | ||
back_populates="accounts", | ||
link_model=UserAccountLink, | ||
sa_relationship_kwargs={"lazy": "selectin"}, | ||
) | ||
|
||
__table_args__ = ( | ||
UniqueConstraint( | ||
"creator_id", | ||
"name", | ||
name="unique_name_by_creator", | ||
), | ||
) | ||
|
||
|
||
class AccountSerializer(BaseTable): | ||
name: str | ||
image: HttpUrl | None = None | ||
color: str | None = None | ||
creator: User |
Oops, something went wrong.