""" User relation """ from typing import Any from piccolo.utils.pydantic import create_pydantic_model from piccolo.table import Table from piccolo.columns import ( ForeignKey, Varchar, BigSerial, UUID, Date, OnDelete, OnUpdate, Boolean, Email, Bytea, Column, ) from piccolo.columns.defaults.date import DateNow from .media import Media, PublicMediaModel class User(Table, tablename="users"): internal_id = BigSerial(null=False, secret=True) public_id = UUID(primary_key=True) first_name = Varchar(length=64, null=False) last_name = Varchar(length=64, null=True) email_address = Email(secret=True, null=False) username = Varchar(length=32, null=False, unique=True) password_hash = Bytea(null=False, secret=True) profile_picture = ForeignKey( references=Media, on_delete=OnDelete.set_null, on_update=OnUpdate.cascade, null=True, default=None, ) creation_date = Date(secret=True, default=DateNow(), null=False) bio = Varchar(length=4096, null=True, default=None) restricted = Boolean(default=False, null=False) email_verified = Boolean(default=False, null=False, secret=True) verified_account = Boolean(default=False, null=False) deleted = Boolean(default=False, null=False) locale = Varchar(length=12, default="en_US", null=False, secret=True) UserModel = create_pydantic_model( User, nested=True, ) # This madness is needed because we need to exclude # some fields from our public API responses PublicUserModelInternal = create_pydantic_model( User, nested=True, exclude_columns=( User.internal_id, User.deleted, User.restricted, User.locale, User.email_verified, User.email_address, User.password_hash, User.creation_date, ), model_name="PublicUser", ) PrivateUserModelInternal = create_pydantic_model( User, nested=True, exclude_columns=(User.internal_id, User.password_hash, User.deleted), model_name="PrivateUser", ) class PublicUserModel(PublicUserModelInternal): profile_picture: PublicMediaModel | None class PrivateUserModel(PrivateUserModelInternal): profile_picture: PublicMediaModel | None async def get_user_by_column( column: Column, data: Any, include_secrets: bool = False, restricted_ok: bool = False, deleted_ok: bool = False, ) -> UserModel | None: """ Retrieves a user object by a given criteria. Returns None if the user doesn't exist or if it's restricted/deleted (unless restricted_ok and deleted_ok are set accordingly) """ user = ( await User.select( *User.all_columns(), exclude_secrets=not include_secrets, ) .where(column == data) .first() ) if user: # Performs validation user = UserModel(**user) if (user.deleted and not deleted_ok) or (user.restricted and not restricted_ok): return return user return async def get_user_by_id(public_id: UUID, *args, **kwargs) -> UserModel: """ Retrieves a user by its public ID """ return await get_user_by_column(User.public_id, public_id, *args, **kwargs) async def get_user_by_username(username: str, *args, **kwargs) -> UserModel | None: """ Retrieves a user by its public username """ return await get_user_by_column(User.username, username, *args, **kwargs) async def get_user_by_email(email: str, *args, **kwargs) -> UserModel | None: """ Retrieves a user by its email address """ return await get_user_by_column(User.email_address, email, *args, **kwargs)