feat: return members in GET /users/<ref>
This commit is contained in:
parent
03e7fb0bb2
commit
cb19049b97
9 changed files with 91 additions and 8 deletions
|
@ -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
|
||||||
|
|
|
@ -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
28
foxnouns/db/member.py
Normal 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")
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
17
foxnouns/models/fields.py
Normal 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)
|
7
foxnouns/models/member.py
Normal file
7
foxnouns/models/member.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from .user import BaseMemberModel, BaseUserModel
|
||||||
|
|
||||||
|
|
||||||
|
class FullMemberModel(BaseMemberModel):
|
||||||
|
user: BaseUserModel
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue