Merge pull request 'Done setup template' (#2) from improvement/route into main

Reviewed-on: sam/fuware#2
This commit is contained in:
Sam Liu 2024-06-01 12:35:24 +00:00
commit a3e575ef41
104 changed files with 313 additions and 256 deletions

View File

@ -1,8 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports": "always",
"source.fixAll": "always",
"source.fixAll.eslint": "explicit"
"source.fixAll": "always"
},
"editor.suggest.preview": true,
"editor.inlayHints.enabled": "offUnlessPressed",

View File

@ -15,9 +15,9 @@ tasks:
py:dev:
desc: runs the backend server
cmds:
- poetry run python fuware/app.py
- poetry run python backend/app.py
ui:dev:
desc: runs the frontend server
dir: fuware-fe
dir: frontend
cmds:
- pnpm dev

View File

@ -5,8 +5,8 @@ from sqlalchemy import pool
from alembic import context
from fuware.core.config import get_app_settings
from fuware.db.models._model_base import SqlAlchemyBase
from backend.core.config import get_app_settings
from backend.db.models._model_base import SqlAlchemyBase
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.

View File

@ -1,15 +1,14 @@
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from mimetypes import init
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fuware.core.config import get_app_settings
from fuware.core.root_logger import get_logger
from fuware.routes import router
from fuware import __version__
from backend.core.config import get_app_settings
from backend.core.root_logger import get_logger
from backend.routes import router
from backend import __version__
import uvicorn
settings = get_app_settings()
@ -22,7 +21,7 @@ fuware is a web application for managing your hours items and tracking them.
@asynccontextmanager
async def lifespan_fn(_: FastAPI) -> AsyncGenerator[None, None]:
logger.info("start: database initialization")
import fuware.db.init_db as init_db
import backend.db.init_db as init_db
init_db.main()
logger.info("end: database initialization")

View File

@ -4,7 +4,7 @@ from pathlib import Path
from dotenv import load_dotenv
from fuware.core.settings.settings import AppSettings, app_settings_constructor
from backend.core.settings import AppSettings, app_settings_constructor
CWD = Path(__file__).parent
BASE_DIR = CWD.parent.parent

View File

@ -1,11 +1,11 @@
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordBearer
from fuware.core.config import get_app_settings
from fuware.core import MessageCode
from backend.core.config import get_app_settings
from backend.core import MessageCode
import jwt
from fuware.services.user.user_service import UserService
from backend.services.user.user_service import UserService
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
oauth2_scheme_soft_fail = OAuth2PasswordBearer(tokenUrl="/api/auth/token", auto_error=False)

View File

@ -2,7 +2,7 @@ from functools import lru_cache
from typing import Protocol
import bcrypt
from fuware.core.config import get_app_settings
from backend.core.config import get_app_settings
class Hasher(Protocol):

View File

@ -4,9 +4,9 @@ from pathlib import Path
import jwt
from fuware.core.config import get_app_settings
from fuware.core import root_logger
from fuware.core.security.hasher import get_hasher
from backend.core.config import get_app_settings
from backend.core import root_logger
from backend.core.security.hasher import get_hasher
ALGORITHM = "HS256"

View File

@ -1,5 +1,5 @@
from pathlib import Path
from fuware.core.settings.db_providers import AbstractDBProvider, SQLiteProvider
from backend.core.settings.db_providers import AbstractDBProvider, SQLiteProvider
from pydantic_settings import BaseSettings # type: ignore

View File

@ -3,7 +3,7 @@ from contextlib import contextmanager
from sqlalchemy.orm.session import Session
from sqlalchemy import create_engine, event, Engine
from sqlalchemy.orm import scoped_session, sessionmaker
from fuware.core.config import get_app_settings
from backend.core.config import get_app_settings
settings = get_app_settings()

View File

@ -7,12 +7,11 @@ from sqlalchemy import engine, orm, text
from alembic import command, config, script
from alembic.config import Config
from alembic.runtime import migration
from fuware.core import root_logger
from fuware.core.config import get_app_settings
from fuware.db.db_setup import session_context
from fuware.repos.repository_users import RepositoryUsers
from fuware.repos.seeder import default_users_init
from fuware.db.models._model_base import Model
from backend.core import root_logger
from backend.core.config import get_app_settings
from backend.db.db_setup import session_context
from backend.repos.repository_users import RepositoryUsers
from backend.repos.seeder import default_users_init
# from fuware.db.models import User
PROJECT_DIR = Path(__file__).parent.parent.parent

View File

@ -1,10 +1,10 @@
from datetime import datetime
from sqlalchemy import DateTime, Integer
from sqlalchemy import DateTime
from sqlalchemy.orm import declarative_base, Mapped, mapped_column
from text_unidecode import unidecode
from fuware.db.db_setup import SessionLocal
from backend.db.db_setup import SessionLocal
Model = declarative_base()
Model.query = SessionLocal.query_property()

View File

@ -1,6 +1,6 @@
import uvicorn
from fuware.app import settings
from backend.app import settings
def main():

View File

@ -1,11 +1,11 @@
from fuware.core.config import get_app_settings
from fuware.core.security.security import hash_password
from fuware.db.models import User
from fuware.schemas import UserCreate
from backend.core.config import get_app_settings
from backend.core.security.security import hash_password
from backend.db.models import User
from backend.schemas import UserCreate
from sqlalchemy.orm import Session
from uuid import UUID
from fuware.schemas.user.user import UserSeeds
from backend.schemas.user.user import UserSeeds
settings = get_app_settings()

View File

@ -1,9 +1,9 @@
from fuware.core.config import get_app_settings
from fuware.core.root_logger import get_logger
from fuware.repos.repository_users import RepositoryUsers
from backend.core.config import get_app_settings
from backend.core.root_logger import get_logger
from backend.repos.repository_users import RepositoryUsers
from sqlalchemy.orm import Session
from fuware.schemas.user import UserSeeds
from backend.schemas.user import UserSeeds
logger = get_logger("init_users")

View File

@ -1,7 +1,7 @@
from enum import Enum
from fastapi import APIRouter, Depends
from fuware.core.dependencies import get_auth_user
from backend.core.dependencies import get_auth_user
class PrivateAPIRouter(APIRouter):

View File

@ -5,12 +5,12 @@ from fastapi import APIRouter, Depends, HTTPException, Response, status
# from fastapi.encoders import jsonable_encoder
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from fuware.core.config import get_app_settings
from fuware.core.dependencies.dependencies import get_current_user
from fuware.core import MessageCode
from fuware.db.db_setup import generate_session
from fuware.schemas import ReturnValue, UserRequest, LoginResponse, UserCreate, PrivateUser
from fuware.services.user import UserService
from backend.core.config import get_app_settings
from backend.core.dependencies.dependencies import get_current_user
from backend.core import MessageCode
from backend.db.db_setup import generate_session
from backend.schemas import ReturnValue, UserRequest, LoginResponse, UserCreate, PrivateUser
from backend.services.user import UserService
auth_router = APIRouter(tags=["Users: Authentication"])

View File

@ -1,12 +1,12 @@
from typing import Annotated, Any
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from fuware.core.config import get_app_settings
from fuware.core.dependencies import is_logged_in
from fuware.db.db_setup import generate_session
from fuware.schemas.common import ReturnValue
from fuware.schemas.user import ProfileResponse
from fuware.services.user import UserService
from backend.core.config import get_app_settings
from backend.core.dependencies import is_logged_in
from backend.db.db_setup import generate_session
from backend.schemas.common import ReturnValue
from backend.schemas.user import ProfileResponse
from backend.services.user import UserService
public_router = APIRouter(tags=["Users: Info"])

View File

@ -3,7 +3,7 @@ from uuid import UUID
from pydantic import ConfigDict
from fastapi import Form
from fuware.schemas.fuware_model import FuwareModel
from backend.schemas.fuware_model import FuwareModel
class UserBase(FuwareModel):
username: str = Form(...)
@ -30,7 +30,6 @@ class PrivateUser(UserBase):
class ProfileResponse(UserBase):
name: str
is_admin: bool
is_lock: bool
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@ -1,4 +1,4 @@
from fuware.core.config import get_app_settings
from backend.core.config import get_app_settings
class BaseService:

View File

@ -1,10 +1,10 @@
from sqlalchemy.orm import Session
from fuware.core.security.hasher import get_hasher
from fuware.core.security import create_access_token
from fuware.core.security.security import create_refresh_token
from fuware.repos import RepositoryUsers
from fuware.schemas import UserRequest, UserCreate
from fuware.services._base_service import BaseService
from backend.core.security.hasher import get_hasher
from backend.core.security import create_access_token
from backend.core.security.security import create_refresh_token
from backend.repos import RepositoryUsers
from backend.schemas import UserRequest, UserCreate
from backend.services._base_service import BaseService
class UserService(BaseService):
def __init__(self):

View File

@ -8,7 +8,7 @@
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
rel="stylesheet"
/>
<title>Vite + Solid</title>
<title>Fuware</title>
</head>
<body>
<div id="root"></div>

View File

@ -1,5 +1,5 @@
{
"name": "fuware-fe",
"name": "fuware",
"private": true,
"version": "0.0.0",
"type": "module",

View File

Before

Width:  |  Height:  |  Size: 909 KiB

After

Width:  |  Height:  |  Size: 909 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -9,20 +9,16 @@
}
#main-page {
height: calc(100svh - 56px);
height: calc(100svh - 64px);
display: flex;
overflow: hidden;
}
#main-page.login-page {
height: 100svh;
}
#main-page .navbar {
width: 350px;
border-right: 1px solid #dedede;
}
#main-page .main-content {
width: calc(100vw - 350px);
max-height: calc(100svh - 64px);
overflow-y: auto;
}

23
frontend/src/App.jsx Normal file
View File

@ -0,0 +1,23 @@
import { MetaProvider } from '@solidjs/meta'
import { Toaster } from 'solid-toast'
import './App.css'
import { SiteContextProvider } from './context/SiteContext'
function App(props) {
return (
<MetaProvider>
<SiteContextProvider>
<Toaster
containerStyle={
props.location?.pathname.indexOf('/login') >= 0
? null
: { 'margin-top': '60px' }
}
/>
{props.children}
</SiteContextProvider>
</MetaProvider>
)
}
export default App

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,79 @@
import { getProfile } from '@api/user'
import fuwareLogo from '@assets/logo-fuware.svg'
import { useSiteContext } from '@context/SiteContext'
import useAuth from '@hooks/useAuth'
import useToast from '@hooks/useToast'
import { A } from '@solidjs/router'
import { IconLogout, IconMenuDeep } from '@tabler/icons-solidjs'
import { Show, onMount } from 'solid-js'
export default function Header() {
const { store, setAuth, setUser } = useSiteContext()
const { clickLogOut } = useAuth(setAuth)
const notify = useToast()
onMount(async () => {
try {
const resp = await getProfile()
if (resp.status === 200) {
setUser(resp.data)
}
} catch (error) {
notify.error({
title: 'Get profile fail!',
closable: false,
description: error?.data || 'Can not get user profile!',
})
}
})
const logOut = async () => {
try {
await clickLogOut()
} catch (error) {
console.log({
status: 'danger',
title: 'Logout fail!',
closable: false,
})
}
}
return (
<header class="w-full navbar py-3 px-4 items-center justify-between bg-emerald-500">
<div class="flex items-center justify-end">
<A href="/" class="text-white flex items-center hover:text-white">
<img src={fuwareLogo} class="w-8" alt="Fuware logo" />
<span class="ml-2 text-2xl">Fuware</span>
</A>
</div>
<Show when={store.auth}>
<div class="flex items-center justify-end">
<div class="avatar hidden lg:block">
<div class="w-9 mask mask-hexagon">
<img
src={`https://ui-avatars.com/api/?name=${store.userInfo?.name}`}
alt="avatar"
/>
</div>
</div>
<A
href="/me"
class="mx-3 text-white hover:text-white hidden lg:block"
>
{store.userInfo?.name}
</A>
<button class="btn btn-ghost btn-sm hidden lg:block" onClick={logOut}>
<IconLogout size={16} />
</button>
<label
for="nav-menu"
class="btn btn-ghost btn-sm drawer-button pr-0 lg:hidden"
>
<IconMenuDeep size={25} color="white" />
</label>
</div>
</Show>
</header>
)
}

View File

@ -2,22 +2,12 @@
import { useSiteContext } from '@context/SiteContext'
import useAuth from '@hooks/useAuth'
import useLanguage from '@hooks/useLanguage'
import { NAV_ROUTES } from '@routes/routes'
import { A } from '@solidjs/router'
import { IconDashboard, IconLogout, IconTriangle } from '@tabler/icons-solidjs'
import { IconLogout, IconTriangle } from '@tabler/icons-solidjs'
import { For, Show } from 'solid-js'
import { Dynamic } from 'solid-js/web'
const language = useLanguage('vi')
const NAVBAR_ITEM = [
{
path: '/dashboard',
icon: IconDashboard,
text: language?.ui.dashboard,
},
]
export default function Navbar() {
const { store, setAuth } = useSiteContext()
const { clickLogOut } = useAuth(setAuth)
@ -35,7 +25,7 @@ export default function Navbar() {
}
return (
<div class="drawer-side">
<div class="drawer-side lg:h-[calc(100svh-64px)]">
<label for="nav-menu" aria-label="close sidebar" class="drawer-overlay" />
<div class="bg-base-200 w-80 min-h-full">
<Show when={store.auth}>
@ -48,7 +38,9 @@ export default function Navbar() {
/>
</div>
</div>
<span class="mx-3 line-clamp-1">{store.userInfo?.name}</span>
<span class="mx-3 line-clamp-1">
<A href="/me">{store.userInfo?.name}</A>
</span>
<button class="btn btn-ghost btn-sm" onClick={logOut}>
<IconLogout size={16} />
</button>
@ -58,15 +50,17 @@ export default function Navbar() {
</div>
</Show>
<ul class="menu p-4 w-80 text-base-content">
<For each={NAVBAR_ITEM}>
{(item) => (
<li>
<A href={item.path}>
<Dynamic component={item.icon} />
{item.text}
</A>
</li>
)}
<For each={NAV_ROUTES}>
{(item) =>
item.show && (
<li class="mb-2">
<A href={item.path}>
<Dynamic component={item.icon} />
{item.text}
</A>
</li>
)
}
</For>
</ul>
</div>

View File

@ -41,6 +41,7 @@ export function SiteContextProvider(props) {
s.userInfo = user
}),
)
setLocalStore()
}
return (

View File

@ -4,7 +4,8 @@
"password": "Mật khẩu",
"login": "Đăng Nhập",
"logout": "Đăng xuất",
"dashboard": "Bảng điều khiển"
"dashboard": "Bảng điều khiển",
"profile": "Hồ sơ"
},
"message": {
"CREATED_USER": "Username already registered!",

View File

@ -1,3 +1,4 @@
import { NAV_ROUTES } from '@routes/routes'
import { useNavigate } from '@solidjs/router'
import { onMount } from 'solid-js'
@ -5,7 +6,8 @@ export default function Home() {
const navigate = useNavigate()
onMount(() => {
navigate('/dashboard', { replace: true })
const first = NAV_ROUTES.filter((item) => item.show)[0]?.path || '/me'
navigate(first, { replace: true })
})
return <></>

View File

@ -20,7 +20,7 @@ export default function Layout(props) {
<div id="main-page">
<div class="drawer lg:drawer-open">
<input id="nav-menu" type="checkbox" class="drawer-toggle" />
<div class="drawer-content">
<div class="drawer-content flex flex-col">
<main class="main-content p-3">{props.children}</main>
</div>
<Navbar />

View File

@ -0,0 +1,3 @@
export default function Profile() {
return <>Profile</>
}

View File

@ -0,0 +1,42 @@
import useLanguage from '@hooks/useLanguage'
import { IconDashboard } from '@tabler/icons-solidjs'
import UserHelper from '@utils/auth'
import { lazy } from 'solid-js'
const language = useLanguage()
const userHelper = new UserHelper()
console.log(userHelper.isAdmin)
export const NAV_ROUTES = [
{
path: '/dashboard',
components: lazy(() => import('@pages/Dashboard')),
filter: {},
show: false,
icon: IconDashboard,
text: language?.ui.dashboard,
},
{
path: '/profile',
components: lazy(() => import('@pages/Profile')),
filter: {},
show: true,
icon: IconDashboard,
text: language?.ui.profile,
},
]
export const ROUTES = [
{
path: '/',
components: lazy(() => import('@pages/Home')),
filter: {},
},
...NAV_ROUTES,
{
path: '/me',
components: lazy(() => import('@pages/Profile')),
filter: {},
},
]

View File

@ -0,0 +1,14 @@
import { STORE_KEY } from './enum'
import { Helpers } from './helper'
export default class UserHelper {
#user
constructor() {
this.#user = Helpers.decrypt(localStorage.getItem(STORE_KEY))
}
get isAdmin() {
return this.#user?.userInfo?.isAdmin
}
}

View File

@ -38,10 +38,6 @@ export class Helpers {
return exp < currentTime
}
static checkAuth = () => {
return !!this.getCookie(LOGIN_KEY) && !!localStorage.getItem(STORE_KEY)
}
static encrypt = (obj) => {
return AES.encrypt(JSON.stringify(obj), SECRET_KEY).toString()
}

View File

@ -8,7 +8,7 @@ const _dirname = dirname(fileURLToPath(import.meta.url))
// https://vitejs.dev/config/
// production
export default defineConfig(({ command, mode }) => {
export default defineConfig(({ mode }) => {
// eslint-disable-next-line no-undef
const env = loadEnv(mode, process.cwd(), '')

View File

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 KiB

View File

@ -1,20 +0,0 @@
import { Toaster } from 'solid-toast'
import './App.css'
import { SiteContextProvider } from './context/SiteContext'
function App(props) {
return (
<SiteContextProvider>
<Toaster
containerStyle={
props.location?.pathname.indexOf('/login') >= 0
? null
: { 'margin-top': '60px' }
}
/>
{props.children}
</SiteContextProvider>
)
}
export default App

View File

@ -1,88 +0,0 @@
import { getProfile } from '@api/user'
import fuwareLogo from '@assets/logo-fuware.svg'
import { useSiteContext } from '@context/SiteContext'
import useAuth from '@hooks/useAuth'
import useToast from '@hooks/useToast'
import { A } from '@solidjs/router'
import { IconLogout, IconMenuDeep } from '@tabler/icons-solidjs'
import { Show, onMount } from 'solid-js'
import { css } from 'solid-styled-components'
export default function Header() {
const { store, setAuth, setUser } = useSiteContext()
const { clickLogOut } = useAuth(setAuth)
const notify = useToast()
onMount(async () => {
try {
const resp = await getProfile()
if (resp.status === 200) {
setUser(resp.data)
}
} catch (error) {
notify.error({
title: 'Get profile fail!',
closable: false,
description: error?.data || 'Can not get user profile!',
})
}
})
const logOut = async () => {
try {
await clickLogOut()
} catch (error) {
console.log({
status: 'danger',
title: 'Logout fail!',
closable: false,
})
}
}
return (
<header>
<div class="flex py-3 px-4 items-center justify-between bg-emerald-500">
<div class="flex items-center justify-end">
<A href="/" class="text-white flex items-center hover:text-white">
<img
src={fuwareLogo}
class={css`
width: 30px;
`}
alt="Fuware logo"
/>
<span class="ml-2 text-2xl">Fuware</span>
</A>
</div>
<Show when={store.auth}>
<div class="flex items-center justify-end">
<div class="avatar hidden lg:block">
<div class="w-9 mask mask-hexagon">
<img
src={`https://ui-avatars.com/api/?name=${store.userInfo?.name}`}
alt="avatar"
/>
</div>
</div>
<span class="mx-3 text-white hidden lg:block">
{store.userInfo?.name}
</span>
<button
class="btn btn-ghost btn-sm hidden lg:block"
onClick={logOut}
>
<IconLogout size={16} />
</button>
<label
for="nav-menu"
class="btn btn-ghost btn-sm drawer-button pr-0 lg:hidden"
>
<IconMenuDeep size={25} color="white" />
</label>
</div>
</Show>
</div>
</header>
)
}

View File

@ -1,21 +0,0 @@
import { lazy } from 'solid-js'
export const ROUTES = [
{
path: '/',
components: lazy(() => import('@pages/Home')),
filter: {},
},
{
path: '/dashboard',
components: lazy(() => import('@pages/Dashboard')),
filter: {},
},
// {
// path: '/champions/:tab',
// components: lazy(() => import('@pages/Champion')),
// filter: {
// tab: ['list', 'favourite'],
// },
// },
]

Some files were not shown because too many files have changed in this diff Show More