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 .user import User, Token, AuthMethod, FediverseApp
from .member import Member

View file

@ -1,4 +1,5 @@
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
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
import enum
from typing import Any
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 .base import Base
@ -19,17 +20,27 @@ class User(Base):
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=[])
tokens: Mapped[list["Token"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
auth_methods: Mapped[list["AuthMethod"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
members: Mapped[list["Member"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
def __repr__(self):
return f"User(id={self.id!r}, username={self.username!r})"
from .member import Member
class Token(Base):
__tablename__ = "tokens"
@ -40,7 +51,7 @@ class Token(Base):
scopes: Mapped[list[str]] = mapped_column(ARRAY(Text), nullable=False)
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):
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)
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(
ForeignKey("fediverse_apps.id"), nullable=True
)
fediverse_app: Mapped["FediverseApp"] = relationship()
fediverse_app: Mapped["FediverseApp"] = relationship(lazy="immediate")
class FediverseInstanceType(enum.IntEnum):

View file

@ -4,9 +4,11 @@ from itsdangerous import BadSignature
from itsdangerous.url_safe import URLSafeTimedSerializer
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, insert
from sqlalchemy.orm import selectinload
from quart import g
from .user import User, Token
from .member import Member
from foxnouns.exceptions import ForbiddenError, ErrorCode
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.
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" in g:
@ -37,6 +39,13 @@ async def user_from_ref(session: AsyncSession, user_ref: str):
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)

View file

@ -1,7 +1,6 @@
from typing import Any
from pydantic import BaseModel, field_validator
class BasePatchModel(BaseModel):
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
class UserModel(BaseSnowflakeModel):
class BaseUserModel(BaseSnowflakeModel):
name: str = Field(alias="username")
display_name: 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):
pass