feat: return members in GET /users/<ref>

This commit is contained in:
sam 2024-03-20 16:26:34 +01:00
parent 03e7fb0bb2
commit cb19049b97
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
9 changed files with 91 additions and 8 deletions

View file

@ -1,2 +1,3 @@
from .base import Base from .base import Base
from .user import User, Token, AuthMethod, FediverseApp from .user import User, Token, AuthMethod, FediverseApp
from .member import Member

View file

@ -1,4 +1,5 @@
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase): class Base(DeclarativeBase):
pass pass

28
foxnouns/db/member.py Normal file
View file

@ -0,0 +1,28 @@
from typing import Any
from sqlalchemy import Text, BigInteger, ForeignKey
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
from .snowflake import Snowflake
from .user import User
class Member(Base):
__tablename__ = "members"
id: Mapped[int] = mapped_column(
BigInteger(), primary_key=True, default=Snowflake.generate_int
)
name: Mapped[str] = mapped_column(Text(), nullable=False)
display_name: Mapped[str | None] = mapped_column(Text(), nullable=True)
bio: Mapped[str | None] = mapped_column(Text(), nullable=True)
names: Mapped[list[Any]] = mapped_column(JSONB(), nullable=False, default=[])
pronouns: Mapped[list[Any]] = mapped_column(JSONB(), nullable=False, default=[])
fields: Mapped[list[Any]] = mapped_column(JSONB(), nullable=False, default=[])
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped[User] = relationship(back_populates="members", lazy="immediate")

View file

@ -1,8 +1,9 @@
from datetime import datetime from datetime import datetime
import enum import enum
from typing import Any
from sqlalchemy import Text, Integer, BigInteger, ForeignKey, DateTime from sqlalchemy import Text, Integer, BigInteger, ForeignKey, DateTime
from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.dialects.postgresql import ARRAY, JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base from .base import Base
@ -19,17 +20,27 @@ class User(Base):
display_name: Mapped[str | None] = mapped_column(Text(), nullable=True) display_name: Mapped[str | None] = mapped_column(Text(), nullable=True)
bio: Mapped[str | None] = mapped_column(Text(), nullable=True) bio: Mapped[str | None] = mapped_column(Text(), nullable=True)
names: Mapped[list[Any]] = mapped_column(JSONB(), nullable=False, default=[])
pronouns: Mapped[list[Any]] = mapped_column(JSONB(), nullable=False, default=[])
fields: Mapped[list[Any]] = mapped_column(JSONB(), nullable=False, default=[])
tokens: Mapped[list["Token"]] = relationship( tokens: Mapped[list["Token"]] = relationship(
back_populates="user", cascade="all, delete-orphan" back_populates="user", cascade="all, delete-orphan"
) )
auth_methods: Mapped[list["AuthMethod"]] = relationship( auth_methods: Mapped[list["AuthMethod"]] = relationship(
back_populates="user", cascade="all, delete-orphan" back_populates="user", cascade="all, delete-orphan"
) )
members: Mapped[list["Member"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
def __repr__(self): def __repr__(self):
return f"User(id={self.id!r}, username={self.username!r})" return f"User(id={self.id!r}, username={self.username!r})"
from .member import Member
class Token(Base): class Token(Base):
__tablename__ = "tokens" __tablename__ = "tokens"
@ -40,7 +51,7 @@ class Token(Base):
scopes: Mapped[list[str]] = mapped_column(ARRAY(Text), nullable=False) scopes: Mapped[list[str]] = mapped_column(ARRAY(Text), nullable=False)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped[User] = relationship(back_populates="tokens") user: Mapped[User] = relationship(back_populates="tokens", lazy="immediate")
def __repr__(self): def __repr__(self):
return f"Token(id={self.id!r}, user={self.user_id!r})" return f"Token(id={self.id!r}, user={self.user_id!r})"
@ -78,12 +89,12 @@ class AuthMethod(Base):
remote_username: Mapped[str | None] = mapped_column(Text(), nullable=True) remote_username: Mapped[str | None] = mapped_column(Text(), nullable=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped[User] = relationship(back_populates="auth_methods") user: Mapped[User] = relationship(back_populates="auth_methods", lazy="immediate")
fediverse_app_id: Mapped[int] = mapped_column( fediverse_app_id: Mapped[int] = mapped_column(
ForeignKey("fediverse_apps.id"), nullable=True ForeignKey("fediverse_apps.id"), nullable=True
) )
fediverse_app: Mapped["FediverseApp"] = relationship() fediverse_app: Mapped["FediverseApp"] = relationship(lazy="immediate")
class FediverseInstanceType(enum.IntEnum): class FediverseInstanceType(enum.IntEnum):

View file

@ -4,9 +4,11 @@ from itsdangerous import BadSignature
from itsdangerous.url_safe import URLSafeTimedSerializer from itsdangerous.url_safe import URLSafeTimedSerializer
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, insert from sqlalchemy import select, insert
from sqlalchemy.orm import selectinload
from quart import g from quart import g
from .user import User, Token from .user import User, Token
from .member import Member
from foxnouns.exceptions import ForbiddenError, ErrorCode from foxnouns.exceptions import ForbiddenError, ErrorCode
from foxnouns.settings import SECRET_KEY from foxnouns.settings import SECRET_KEY
@ -15,7 +17,7 @@ async def user_from_ref(session: AsyncSession, user_ref: str):
"""Returns a user from a `user_ref` value. If `user_ref` is `@me`, returns the current user. """Returns a user from a `user_ref` value. If `user_ref` is `@me`, returns the current user.
Otherwise, tries to convert the user to a snowflake ID and queries that. Otherwise, returns a user with that username. Otherwise, tries to convert the user to a snowflake ID and queries that. Otherwise, returns a user with that username.
""" """
query = select(User) query = select(User).options(selectinload(User.members))
if user_ref == "@me": if user_ref == "@me":
if "user" in g: if "user" in g:
@ -37,6 +39,13 @@ async def user_from_ref(session: AsyncSession, user_ref: str):
return await session.scalar(query) return await session.scalar(query)
async def user_members(session: AsyncSession, user: User):
query = select(Member).where(Member.user_id == user.id)
res = await session.scalars(query)
return res.all()
serializer = URLSafeTimedSerializer(SECRET_KEY) serializer = URLSafeTimedSerializer(SECRET_KEY)

View file

@ -1,7 +1,6 @@
from typing import Any
from pydantic import BaseModel, field_validator from pydantic import BaseModel, field_validator
class BasePatchModel(BaseModel): class BasePatchModel(BaseModel):
model_config = {"from_attributes": True} model_config = {"from_attributes": True}

17
foxnouns/models/fields.py Normal file
View file

@ -0,0 +1,17 @@
from pydantic import BaseModel, Field
class FieldEntry(BaseModel):
value: str = Field(max_length=128)
status: str
class ProfileField(BaseModel):
name: str = Field(max_length=128)
entries: list[FieldEntry]
class PronounEntry(BaseModel):
value: str = Field(max_length=128)
status: str
display: str | None = Field(max_length=128, default=None)

View file

@ -0,0 +1,7 @@
from pydantic import Field
from .user import BaseMemberModel, BaseUserModel
class FullMemberModel(BaseMemberModel):
user: BaseUserModel

View file

@ -3,12 +3,22 @@ from pydantic import Field
from . import BaseSnowflakeModel from . import BaseSnowflakeModel
class UserModel(BaseSnowflakeModel): class BaseUserModel(BaseSnowflakeModel):
name: str = Field(alias="username") name: str = Field(alias="username")
display_name: str | None display_name: str | None
bio: str | None bio: str | None
class UserModel(BaseUserModel):
members: list["BaseMemberModel"] = Field(default=[])
class BaseMemberModel(BaseSnowflakeModel):
name: str
display_name: str | None
bio: str | None
class SelfUserModel(UserModel): class SelfUserModel(UserModel):
pass pass