text: from typing import Optional
  import logging
  import string
  import random
  from uuid import UUID
  
  from fastapi import APIRouter, Depends, HTTPException, Body, status, Request
  from pydantic import BaseModel, Field, EmailStr
  from fastapi_jwt_auth import AuthJWT
  
  from asyncpg.exceptions import UniqueViolationError
  
  import cao_common_pb2
  
  from caoloapi.util import db_conn
  from caoloapi.protos import cao_users_pb2, cao_users_pb2_grpc
  from ..queen import queen_channel
  from ..model.auth import hashpw, verifypw
  
  
  router = APIRouter(tags=["users"])
  
  
  class User(BaseModel):
      user_id: UUID
      username: str
      displayname: str
      email: Optional[str] = None
  
  
  async def get_current_user_id(Authorize: AuthJWT = Depends()):
      Authorize.jwt_required()
      return Authorize.get_jwt_subject()
  
  
  @router.get("/myself")
  async def get_myself(current_user=Depends(get_current_user_id), db=Depends(db_conn)):
      current_user = await db.fetchrow(
          """
          SELECT id, username, email, display_name
          FROM user_account
          WHERE id=$1
          """,
          current_user,
      )
      if not current_user:
          raise HTTPException(
              status_code=status.HTTP_401_UNAUTHORIZED, detail="Log in first"
          )
      return User(
          user_id=current_user["id"],
          username=current_user["username"],
          email=current_user["email"],
          displayname=current_user["display_name"],
      )
  
  
  class RegisterForm(BaseModel):
      username: str = Field(..., min_length=3, max_length=125)
      email: EmailStr
      pw: str = Field(
          ...,
          min_length=8,
          max_length=125,
          description="Passwords must contain at least 8 characters",
      )
  
  
  @router.post("/register")
  async def register(
      form_data: RegisterForm = Body(...),
      db=Depends(db_conn),
      queen=Depends(queen_channel),
      Authorize: AuthJWT = Depends(),
  ):
  
      raw_pw = form_data.pw
      salt = "".join(random.choice(string.ascii_letters) for _ in range(10))
  
      pw = hashpw(
          raw_pw,
          salt,
      )
  
      async with db.transaction():
          try:
              res = await db.fetchrow(
                  """
                  INSERT INTO user_account (username, display_name, email, pw, salt)
                  VALUES ($1, $1, $2, $3, $4)
                  RETURNING id
                  """,
                  form_data.username,
                  form_data.email,
                  pw,
                  salt,
              )
  
          except UniqueViolationError as err:
              status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
              detail = ""
              if err.constraint_name == "username_is_unique":
                  status_code = status.HTTP_400_BAD_REQUEST
                  detail = "Username is already in use"
              elif err.constraint_name == "email_is_unique":
                  status_code = status.HTTP_400_BAD_REQUEST
                  detail = "Email is already in use"
              else:
                  logging.exception("Failed to register new user, constraint not handled")
  
              logging.warn(f"Error registering user: {detail}")
              raise HTTPException(status_code=status_code, detail=detail) from err
  
          # NOTE: these two futures could run concurrently,
          # however `db` can not be passed to another coroutine...
          # we could use the connection pool directly instead and try with that...
          await _register_user_in_sim(res["id"], queen)
          access_token = Authorize.create_access_token(subject=str(res["id"]))
          refresh_token = Authorize.create_refresh_token(subject=str(res["id"]))
          return {"access_token": access_token, "refresh_token": refresh_token}
  
  
  async def _register_user_in_sim(userid, queen):
  
      stub = cao_users_pb2_grpc.UsersStub(queen)
      cao_user_id = cao_common_pb2.Uuid()
      cao_user_id.data = userid.bytes
      msg = cao_users_pb2.RegisterUserMsg(userId=cao_user_id)
      await stub.RegisterUser(msg)
  
  
  class UserLoginPayload(BaseModel):
      username: str
      password: str
  
  
  @router.post("/login")
  async def login(
      db=Depends(db_conn),
      user: UserLoginPayload = Body(...),
      Authorize: AuthJWT = Depends(),
  ):
      user_in_db = await db.fetchrow(
          """
          SELECT id, pw, salt
          FROM user_account
          WHERE username=$1
          """,
          user.username,
      )
  
      if not user_in_db:
          raise HTTPException(status_code=400, detail="Incorrect username or password")
  
      if not verifypw(user.password, user_in_db["salt"], user_in_db["pw"]):
          raise HTTPException(status_code=400, detail="Incorrect username or password")
  
      access_token = Authorize.create_access_token(subject=str(user_in_db["id"]))
      refresh_token = Authorize.create_refresh_token(subject=str(user_in_db["id"]))
      return {"access_token": access_token, "refresh_token": refresh_token}
  
  
  @router.post("/refresh")
  async def refresh(Authorize: AuthJWT = Depends()):
      Authorize.jwt_refresh_token_required()
  
      current = Authorize.get_jwt_subject()
      assert current is not None
      access_token = Authorize.create_access_token(subject=current)
      return {"access_token": access_token}
  