update backend

This commit is contained in:
Sam Liu 2024-05-15 09:14:26 +00:00
parent 4a4d8e762c
commit d5c967d2e5
16 changed files with 103 additions and 56 deletions

View File

@ -1,8 +1,8 @@
"""Create User and Session Modal """Create User and Session Modal
Revision ID: 8712dd727be7 Revision ID: 56750c50a8c3
Revises: Revises:
Create Date: 2024-05-11 12:44:09.963953 Create Date: 2024-05-13 12:36:10.095215
""" """
from typing import Sequence, Union from typing import Sequence, Union
@ -12,7 +12,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '8712dd727be7' revision: str = '56750c50a8c3'
down_revision: Union[str, None] = None down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@ -28,7 +28,7 @@ def upgrade() -> None:
sa.Column('is_admin', sa.Boolean(), nullable=True), sa.Column('is_admin', sa.Boolean(), nullable=True),
sa.Column('is_lock', sa.Boolean(), nullable=True), sa.Column('is_lock', sa.Boolean(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True), sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('update_at', sa.DateTime(), nullable=True), sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_index(op.f('ix_users_created_at'), 'users', ['created_at'], unique=False) op.create_index(op.f('ix_users_created_at'), 'users', ['created_at'], unique=False)
@ -38,15 +38,15 @@ def upgrade() -> None:
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True) op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_table('session_login', op.create_table('session_login',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('session', sa.UUID(), nullable=False), sa.Column('session', sa.String(), nullable=False),
sa.Column('user_id', sa.UUID(), nullable=False), sa.Column('user_id', sa.UUID(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True), sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('update_at', sa.DateTime(), nullable=True), sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
) )
op.create_index(op.f('ix_session_login_created_at'), 'session_login', ['created_at'], unique=False) op.create_index(op.f('ix_session_login_created_at'), 'session_login', ['created_at'], unique=False)
op.create_index(op.f('ix_session_login_session'), 'session_login', ['session'], unique=False) op.create_index(op.f('ix_session_login_session'), 'session_login', ['session'], unique=True)
op.create_index(op.f('ix_session_login_user_id'), 'session_login', ['user_id'], unique=True) op.create_index(op.f('ix_session_login_user_id'), 'session_login', ['user_id'], unique=True)
# ### end Alembic commands ### # ### end Alembic commands ###

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

View File

@ -67,7 +67,7 @@ export default function Header() {
colorScheme="info" colorScheme="info"
onClick={logOut} onClick={logOut}
> >
{language.logout} {language.ui.logout}
</Button> </Button>
</Show> </Show>
</Flex> </Flex>

View File

@ -12,7 +12,7 @@ const NAVBAR_ITEM = [
{ {
path: '/dashboard', path: '/dashboard',
icon: IconDashboard, icon: IconDashboard,
text: language?.dashboard, text: language?.ui.dashboard,
}, },
] ]

View File

@ -1,10 +1,14 @@
{ {
"login": "Đăng Nhập", "ui": {
"logout": "Đăng xuất", "username": "Tên người dùng",
"dashboard": "Bảng điều khiển", "password": "Mật khẩu",
"champion": "Tướng", "login": "Đăng Nhập",
"skin": "Trang phục", "logout": "Đăng xuất",
"list": "Danh sách", "dashboard": "Bảng điều khiển"
"favourite": "Yêu thích", },
"bought": "Đã Mua" "message": {
"CREATED_USER": "Username already registered!",
"LOGIN_WRONG": "Your username or password input is wrong!",
"USER_LOCK": "Your Account was locked"
}
} }

View File

@ -23,12 +23,22 @@ const LoginPage = styled('div')`
width: 100%; width: 100%;
height: 100svh; height: 100svh;
display: flex; display: flex;
padding-left: 15px;
padding-right: 15px;
place-items: center; place-items: center;
.login-wrap { .login-wrap {
width: 500px; width: 40%;
max-width: 500px;
min-width: 320px;
margin: 0 auto; margin: 0 auto;
.logo {
width: 40%;
max-width: 150px;
min-width: 100px;
}
.login-box { .login-box {
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.25); box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.25);
border-radius: 5px; border-radius: 5px;
@ -87,14 +97,21 @@ export default function Login() {
<LoginPage> <LoginPage>
<div class="login-wrap"> <div class="login-wrap">
<Center width="100%" mb="$10"> <Center width="100%" mb="$10">
gsdfgj;l <picture class="logo">
<source
srcSet="/images/logo_fuware.png"
type="image/png"
media="(min-width: 600px)"
/>
<img src="/images/logo_fuware.png" alt="logo" />
</picture>
</Center> </Center>
<div className="login-box"> <div class="login-box">
<form autoComplete="off" onSubmit={submit}> <form autoComplete="off" onSubmit={submit}>
<Stack direction="column"> <Stack direction="column">
<Center w="100%" mb="$5"> <Center w="100%" mb="$5">
<Heading level="1" size="xl"> <Heading level="1" size="xl">
{language.login} {language.ui.login}
</Heading> </Heading>
</Center> </Center>
<Field <Field
@ -103,7 +120,9 @@ export default function Login() {
formHandler={formHandler} formHandler={formHandler}
render={(field) => ( render={(field) => (
<FormControl mb="$3" invalid={field.helpers.error}> <FormControl mb="$3" invalid={field.helpers.error}>
<FormLabel for="username">Username:</FormLabel> <FormLabel for="username">
{language.ui.username}:
</FormLabel>
<Input id="username" type="text" {...field.props} /> <Input id="username" type="text" {...field.props} />
<Show when={field.helpers.error}> <Show when={field.helpers.error}>
<FormErrorMessage> <FormErrorMessage>
@ -119,7 +138,9 @@ export default function Login() {
formHandler={formHandler} formHandler={formHandler}
render={(field) => ( render={(field) => (
<FormControl mb="$5" invalid={field.helpers.error}> <FormControl mb="$5" invalid={field.helpers.error}>
<FormLabel for="username">Password:</FormLabel> <FormLabel for="username">
{language.ui.password}:
</FormLabel>
<Input id="password" type="password" {...field.props} /> <Input id="password" type="password" {...field.props} />
<Show when={field.helpers.error}> <Show when={field.helpers.error}>
<FormErrorMessage> <FormErrorMessage>
@ -130,7 +151,7 @@ export default function Login() {
)} )}
/> />
<Button size="sm" type="submit"> <Button size="sm" type="submit">
Login {language.ui.login}
</Button> </Button>
</Stack> </Stack>
</form> </form>

View File

@ -1,3 +1,5 @@
const PRODUCTION = import.meta.env.NODE_ENV === 'production'
export const SECRET_KEY = 'bGV0IGRvIGl0IGZvciBlbmNyeXRo' export const SECRET_KEY = 'bGV0IGRvIGl0IGZvciBlbmNyeXRo'
export const STORE_KEY = 'dXNlciBsb2dpbiBpbmZv' export const STORE_KEY = 'dXNlciBsb2dpbiBpbmZv'
export const LOGIN_KEY = import.meta.env.VITE_LOGIN_KEY export const LOGIN_KEY = PRODUCTION ? import.meta.env.VITE_LOGIN_KEY : 'logcook'

View File

@ -74,12 +74,8 @@ async def unicorn_exception_handler(request: Request, exc: HTTPException):
def api_routers(): def api_routers():
app.include_router(router) app.include_router(router)
api_routers() api_routers()
# app.include_router(authR.authRouter)
# app.include_router(userR.userRouter)
def main(): def main():
uvicorn.run("app:app", host="0.0.0.0", port=settings.API_PORT, reload=True, workers=1, forwarded_allow_ips="*") uvicorn.run("app:app", host="0.0.0.0", port=settings.API_PORT, reload=True, workers=1, forwarded_allow_ips="*")

View File

@ -0,0 +1,8 @@
class MessageCode:
CREATED_USER: str = 'CREATED_USER'
WRONG_INPUT: str = 'LOGIN_WRONG'
ACCOUNT_LOCK: str = 'USER_LOCK'
def message_code():
return MessageCode()

View File

@ -13,7 +13,7 @@ class SqlAlchemyBase(Model):
__abstract__ = True __abstract__ = True
created_at: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.utcnow(), index=True) created_at: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.utcnow(), index=True)
update_at: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.utcnow(), onupdate=datetime.utcnow()) updated_at: Mapped[datetime | None] = mapped_column(DateTime, default=datetime.utcnow(), onupdate=datetime.utcnow())
@classmethod @classmethod
def normalize(cls, val: str) -> str: def normalize(cls, val: str) -> str:

View File

@ -25,7 +25,7 @@ class SessionLogin(SqlAlchemyBase):
__tablename__ = 'session_login' __tablename__ = 'session_login'
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
session: Mapped[str] = mapped_column(UUID, default=uuid4, index=True, nullable=False) session: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), unique=True, index=True, nullable=False) user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), unique=True, index=True, nullable=False)
user = relationship("User", back_populates="session_login") user = relationship("User", back_populates="session_login")

View File

@ -1,8 +1,11 @@
from fuware.core.config import get_app_settings
from fuware.core.security.hasher import get_hasher from fuware.core.security.hasher import get_hasher
from fuware.db.models import SessionLogin, User from fuware.db.models import SessionLogin, User
from fuware.schemas import UserCreate from fuware.schemas import UserCreate
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from uuid import uuid4
settings = get_app_settings()
class RepositoryUsers: class RepositoryUsers:
def __init__(self): def __init__(self):
@ -33,7 +36,8 @@ class RepositoryUsers:
def create_session(self, db: Session, user_id: str): def create_session(self, db: Session, user_id: str):
try: try:
db_ss = SessionLogin(user_id=user_id) bhash = uuid4().hex[:10]
db_ss = SessionLogin(session=bhash,user_id=user_id)
db.add(db_ss) db.add(db_ss)
db.commit() db.commit()
except Exception: except Exception:
@ -50,11 +54,12 @@ class RepositoryUsers:
return db_ss return db_ss
def logout(self, db: Session, user_ss: str): def logout(self, db: Session, user_ss: str):
print(f"Logout: {user_ss}")
db_ss = self.sessionLogin.query.filter_by(session=user_ss).first() db_ss = self.sessionLogin.query.filter_by(session=user_ss).first()
print(f"db_ss: {db_ss}")
try: try:
db.delete(db_ss) db.delete(db_ss)
db.commit() db.commit()
except Exception as e: except Exception as e:
db.rollback() db.rollback()
raise e raise e
pass

View File

@ -1,13 +1,13 @@
from typing import Any from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Response from fastapi import APIRouter, Depends, HTTPException, Response, Request
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from fuware.core.config import get_app_settings from fuware.core.config import get_app_settings
from fuware.core.message_code import message_code
from fuware.core.security.hasher import get_hasher from fuware.core.security.hasher import get_hasher
from fuware.db.db_setup import generate_session from fuware.db.db_setup import generate_session
from fuware.schemas import ReturnValue, UserRequest from fuware.schemas import ReturnValue, UserRequest, PrivateUser, UserCreate
from fuware.schemas.user.user import UserCreate
from fuware.services import UserService from fuware.services import UserService
@ -15,24 +15,29 @@ public_router = APIRouter(tags=["Users: Authentication"])
user_service = UserService() user_service = UserService()
hasher = get_hasher() hasher = get_hasher()
settings = get_app_settings() settings = get_app_settings()
message = message_code()
@public_router.put('/register') @public_router.put('/register')
def register_user(user: UserCreate, db: Session = Depends(generate_session)) -> ReturnValue[Any]: def register_user(user: UserCreate, db: Session = Depends(generate_session)) -> ReturnValue[Any]:
db_user = user_service.get_by_username(username=user.username) db_user = user_service.get_by_username(username=user.username)
if db_user: if db_user:
raise HTTPException(status_code=400, detail="Username already registered!") raise HTTPException(status_code=400, detail=message.CREATED_USER)
user_return = user_service.create(db=db, user=user) user_return = user_service.create(db=db, user=user)
return ReturnValue(status=200, data=jsonable_encoder(user_return)) return ReturnValue(status=200, data=jsonable_encoder(user_return))
@public_router.post('/login', response_model=ReturnValue[Any]) @public_router.post('/login', response_model=ReturnValue[PrivateUser])
def user_login(user: UserRequest, response: Response, db: Session = Depends(generate_session)) -> ReturnValue[Any]: def user_login(user: UserRequest, response: Response, db: Session = Depends(generate_session)) -> ReturnValue[Any]:
db_user = user_service.get_by_username(username=user.username) db_user = user_service.check_exist(user=user)
if not db_user:
raise HTTPException(status_code=401, detail="Your username or password input is wrong!")
if not hasher.verify(password=user.password, hashed=db_user.password):
raise HTTPException(status_code=401, detail="Your username or password input is wrong!")
if db_user.is_lock is True:
raise HTTPException(status_code=401, detail="Your Account was locked")
cookieEncode = user_service.check_login(db=db, user_id=db_user.id) cookieEncode = user_service.check_login(db=db, user_id=db_user.id)
response.set_cookie(key=settings.COOKIE_KEY, value=cookieEncode.session) response.set_cookie(key=settings.COOKIE_KEY, value=cookieEncode, max_age=86400, httponly=True)
return ReturnValue(status=200, data=jsonable_encoder(db_user)) return ReturnValue(status=200, data=db_user)
@public_router.get('/logout', response_model=ReturnValue[Any])
def user_logout(request: Request, response: Response, db: Session = Depends(generate_session)) -> ReturnValue[Any]:
session_id = request.cookies.get(settings.COOKIE_KEY)
if not session_id:
response.delete_cookie(key=settings.COOKIE_KEY)
return ReturnValue(status=200, data='Logged out')
user_service.delete_session(db=db, user_ss=session_id)
response.delete_cookie(key=settings.COOKIE_KEY)
return ReturnValue(status=200, data='Logged out')

View File

@ -1,7 +1,7 @@
from typing import ClassVar, Protocol, TypeVar from typing import ClassVar, TypeVar
from humps import camelize from humps import camelize
from enum import Enum from enum import Enum
from pydantic import UUID4, BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
T = TypeVar("T", bound=BaseModel) T = TypeVar("T", bound=BaseModel)

View File

@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
from pydantic import BaseModel, ConfigDict from uuid import UUID
from pydantic import ConfigDict
from fastapi import Form from fastapi import Form
from fuware.schemas.fuware_model import FuwareModel from fuware.schemas.fuware_model import FuwareModel
@ -15,7 +16,7 @@ class UserCreate(UserRequest):
name: str name: str
class PrivateUser(UserBase): class PrivateUser(UserBase):
id: str id: UUID
name: str name: str
is_admin: bool is_admin: bool
is_lock: bool is_lock: bool

View File

@ -1,12 +1,14 @@
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from fuware.core.message_code import message_code
from fuware.core.security.hasher import get_hasher from fuware.core.security.hasher import get_hasher
from fuware.repos import RepositoryUsers from fuware.repos import RepositoryUsers
from fuware.schemas import UserRequest, UserCreate from fuware.schemas import UserRequest, UserCreate
from fuware.services._base_service import BaseService from fuware.services._base_service import BaseService
hasher = get_hasher() hasher = get_hasher()
message = message_code()
class UserService(BaseService): class UserService(BaseService):
def __init__(self): def __init__(self):
@ -21,16 +23,19 @@ class UserService(BaseService):
def create(self, db: Session, user: UserCreate): def create(self, db: Session, user: UserCreate):
return self.repos.create(db=db, user=user) return self.repos.create(db=db, user=user)
def check_exist(self, db: Session, user: UserRequest): def check_exist(self, user: UserRequest):
db_user = self.get_by_username(username=user.username) db_user = self.get_by_username(username=user.username)
if not db_user: if not db_user:
raise HTTPException(status_code=401, detail="Your username or password input is wrong!") raise HTTPException(status_code=401, detail=message.WRONG_INPUT)
if not hasher.verify(password=user.password, hashed=db_user.password): if not hasher.verify(password=user.password, hashed=db_user.password):
raise HTTPException(status_code=401, detail="Your username or password input is wrong!") raise HTTPException(status_code=401, detail=message.WRONG_INPUT)
if db_user.is_lock is True: if db_user.is_lock is True:
raise HTTPException(status_code=401, detail="Your Account is banned") raise HTTPException(status_code=401, detail=message.ACCOUNT_LOCK)
return db_user return db_user
def check_login(self, db: Session, user_id: str): def check_login(self, db: Session, user_id: str):
db_session = self.repos.login(db=db, user_id=user_id) db_session = self.repos.login(db=db, user_id=user_id)
return db_session return db_session.session
def delete_session(self, db: Session, user_ss: str):
self.repos.logout(db=db, user_ss=user_ss)