diff --git a/foxnouns/blueprints/v2/members.py b/foxnouns/blueprints/v2/members.py index 63778f8..c832b09 100644 --- a/foxnouns/blueprints/v2/members.py +++ b/foxnouns/blueprints/v2/members.py @@ -8,9 +8,7 @@ from foxnouns.db import Member from foxnouns.db.aio import async_session from foxnouns.db.util import user_from_ref, is_self from foxnouns.exceptions import NotFoundError, ErrorCode -from foxnouns.models import BasePatchModel -from foxnouns.models.member import FullMemberModel -from foxnouns.models.fields import ProfileField, FieldEntry, PronounEntry +from foxnouns.models.member import FullMemberModel, MemberPatchModel from foxnouns.settings import BASE_DOMAIN bp = Blueprint("members_v2", __name__) @@ -27,20 +25,19 @@ async def get_members(user_ref: str): return [FullMemberModel.model_validate(m) for m in user.members] -class MemberPostData(BasePatchModel): - name: str = Field(min_length=1, max_length=100) # TODO: validate member names more - bio: str | None = Field(max_length=1024, default=None) - - names: list[FieldEntry] = Field(default=[]) - pronouns: list[PronounEntry] = Field(default=[]) - fields: list[ProfileField] = Field(default=[]) +class MemberCreateModel(MemberPatchModel): + name: str = Field( + min_length=1, + max_length=100, + pattern=r"^[^@\?!#\/\\\[\]\"\{\}'$%&()+<=>^|~`,\*]{1,100}$", + ) @bp.post("/api/v2/members", host=BASE_DOMAIN) @require_auth(scope="member.create") -@validate_request(MemberPostData) +@validate_request(MemberCreateModel) @validate_response(FullMemberModel, 200) -async def create_member(data: MemberPostData): +async def create_member(data: MemberCreateModel): async with async_session() as session: member = Member( user_id=g.user.id, diff --git a/foxnouns/blueprints/v2/users.py b/foxnouns/blueprints/v2/users.py index 5ceb50f..a86c594 100644 --- a/foxnouns/blueprints/v2/users.py +++ b/foxnouns/blueprints/v2/users.py @@ -6,7 +6,8 @@ from sqlalchemy import select from foxnouns.auth import require_auth from foxnouns.db import User from foxnouns.db.aio import async_session -from foxnouns.db.util import user_from_ref, is_self +from foxnouns.db.snowflake import Snowflake +from foxnouns.db.util import user_from_ref, is_self, create_token, generate_token from foxnouns.exceptions import NotFoundError, ErrorCode from foxnouns.models import BasePatchModel from foxnouns.models.user import UserModel, SelfUserModel, check_username @@ -48,6 +49,8 @@ class EditUserRequest(BasePatchModel): @validate_request(EditUserRequest) @validate_response(SelfUserModel, 200) async def edit_user(data: EditUserRequest): + """Updates the current user.""" + async with async_session() as session: user = await session.scalar(select(User).where(User.id == g.user.id)) @@ -61,3 +64,32 @@ async def edit_user(data: EditUserRequest): await session.commit() return SelfUserModel.model_validate(user) + + +class DebugUserData(BasePatchModel): + username: str + + +class DebugUserResponse(SelfUserModel): + token: str + + +@bp.post("/api/v2/users/debug", host=BASE_DOMAIN) +@validate_request(DebugUserData) +@validate_response(DebugUserResponse, 200) +async def debug_create_user(data: DebugUserData): + """Creates a user from just a username, and returns it along with a token. + FIXME: this must be removed **BEFORE** deploying to production (or even public testing) + """ + + async with async_session() as session: + user = User(id=Snowflake.generate_int(), username=data.username) + await session.commit() + + session.add(user) + token = await create_token(session, user, ["*"]) + await session.commit() + await user.awaitable_attrs.members + + user.token = generate_token(token) + return DebugUserResponse.model_validate(user) diff --git a/foxnouns/models/member.py b/foxnouns/models/member.py index cfb0354..1ab7162 100644 --- a/foxnouns/models/member.py +++ b/foxnouns/models/member.py @@ -1,7 +1,30 @@ -from pydantic import Field +from pydantic import Field, field_validator +from . import BasePatchModel +from .fields import FieldEntry, ProfileField, PronounEntry from .user import BaseMemberModel, BaseUserModel class FullMemberModel(BaseMemberModel): user: BaseUserModel + + +class MemberPatchModel(BasePatchModel): + name: str | None = Field( + min_length=1, + max_length=100, + default=None, + pattern=r"^[^@\?!#\/\\\[\]\"\{\}'$%&()+<=>^|~`,\*]{1,100}$", + ) + bio: str | None = Field(max_length=1024, default=None) + + names: list[FieldEntry] = Field(default=[]) + pronouns: list[PronounEntry] = Field(default=[]) + fields: list[ProfileField] = Field(default=[]) + + @field_validator("name") + @classmethod + def check_name(cls, value): + if value in [".", "..", "edit"]: + raise ValueError("Name is not allowed") + return value