slim_cli/template/api/user.py

Summary

Maintainability
A
45 mins
Test Coverage
import binascii
from typing import Dict, List, Union, Type

from slim.base.view import BaseView
from slim.ext.decorator import require_role
from slim.utils import sentinel, to_bin
from slim.base.user import BaseAccessTokenUserViewMixin, BaseUserViewMixin, BaseUser
from slim.base.sqlquery import SQLValuesToWrite, DataRecord
from slim.retcode import RETCODE
from slim.support.peewee import PeeweeView

from app import app
from api.validate.user import SigninDataModel, SignupDirectDataModel
from api import run_in_thread
from model.user import User
from model.user_token import UserToken
from permissions.role_define import ACCESS_ROLE


class UserMixin(BaseAccessTokenUserViewMixin):
    """ 用户Mixin,用于View继承 """
    def get_user_by_token(self: Union['BaseUserViewMixin', 'BaseView'], token) -> Type[BaseUser]:
        t = UserToken.get_by_token(token)
        if t: return User.get_by_pk(t.user_id)

    async def setup_user_token(self, user_id, key=None, expires=30):
        """ signin, setup user token """
        t = UserToken.new(user_id)
        await t.init(self)
        return t

    def teardown_user_token(self: Union['BaseUserViewMixin', 'BaseView'], token=sentinel):
        """ signout, invalidate the token here """
        u = self.current_user
        if u:
            if token is None:
                # clear all tokens
                UserToken.delete().where(UserToken.user_id == u.id).execute()
                return

            if token is sentinel:
                # clear current token
                try:
                    token = to_bin(self.get_user_token())
                except binascii.Error:
                    return
            UserToken.delete().where(UserToken.user_id == u.id, UserToken.id == token).execute()


@app.route.view('user', 'User API')
class UserView(PeeweeView, UserMixin):
    model = User

    @classmethod
    def interface_register(cls):
        super().interface_register()
        cls.unregister('new')
        cls.unregister('delete')

    @app.route.interface('POST', summary='注册', va_post=SignupDirectDataModel)
    async def signup(self):
        """
        用户注册接口
        User Signup Interface
        """
        vpost: SignupDirectDataModel = self._.validated_post

        u = User.new(vpost.username, vpost.password, email=vpost.email, nickname=vpost.nickname)
        if not u:
            self.finish(RETCODE.FAILED, msg='注册失败!')
        else:
            t: UserToken = await self.setup_user_token(u.id)
            self.finish(RETCODE.SUCCESS, {'id': u.id, 'username': u.username, 'access_token': t.get_token()})

    @app.route.interface('POST', summary='登录', va_post=SigninDataModel)
    async def signin(self):
        """
        用户登录接口
        User Signin Interface
        """
        vpost: SigninDataModel = self._.validated_post

        # check auth method
        if vpost.email:
            field_value = vpost.email
            auth_method = User.auth_by_mail
        elif vpost.username:
            field_value = vpost.username
            auth_method = User.auth_by_username
        else:
            return self.finish(RETCODE.FAILED, msg='必须提交用户名或邮箱中的一个作为登录凭据')

        # auth and generate access token
        user = await run_in_thread(auth_method, field_value, vpost.password)

        if user:
            t: UserToken = await self.setup_user_token(user.id)
            self.finish(RETCODE.SUCCESS, {'id': user.id, 'access_token': t.get_token()})
        else:
            self.finish(RETCODE.FAILED, msg='用户名或密码不正确')

    @app.route.interface('POST', summary='登出')
    @require_role(ACCESS_ROLE.USER)
    async def signout(self):
        """
        退出登录
        """
        if self.current_user:
            self.teardown_user_token()
            self.finish(RETCODE.SUCCESS)
        else:
            self.finish(RETCODE.FAILED)