Renamed hooks on frontend and started working on shop registration

This commit is contained in:
Thastertyn 2025-03-13 15:41:44 +01:00
parent 3b42ea6914
commit 9c2b88b103
35 changed files with 180 additions and 37 deletions

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter from fastapi import APIRouter
from app.api.routes import cart_routes, login_routes, shop, user_routes, utils_routes from app.api.routes import cart_routes, login_routes, shop, user_routes, utils_routes, shop_routes
api_router = APIRouter() api_router = APIRouter()
@ -9,3 +9,4 @@ api_router.include_router(user_routes.router)
api_router.include_router(utils_routes.router) api_router.include_router(utils_routes.router)
api_router.include_router(login_routes.router) api_router.include_router(login_routes.router)
api_router.include_router(shop.shop_router) api_router.include_router(shop.shop_router)
api_router.include_router(shop_routes.router)

View File

@ -2,7 +2,7 @@ from typing import Annotated
import jwt import jwt
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError from jwt.exceptions import InvalidTokenError
from pydantic import ValidationError from pydantic import ValidationError
from sqlmodel import Session from sqlmodel import Session
@ -19,6 +19,7 @@ reusable_oauth2 = OAuth2PasswordBearer(
SessionDep = Annotated[Session, Depends(get_session)] SessionDep = Annotated[Session, Depends(get_session)]
TokenDep = Annotated[str, Depends(reusable_oauth2)] TokenDep = Annotated[str, Depends(reusable_oauth2)]
LoginDep = Annotated[OAuth2PasswordRequestForm, Depends()]
def get_current_user(session: SessionDep, token: TokenDep) -> User: def get_current_user(session: SessionDep, token: TokenDep) -> User:

View File

@ -1,24 +1,32 @@
from datetime import timedelta from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from app.api.dependencies import SessionDep from app.api.dependencies import LoginDep, SessionDep
from app.core import security from app.core import security
from app.core.config import settings from app.core.config import settings
from app.crud import user_crud from app.crud import user_crud
from app.schemas.user_schemas import Token from app.schemas.user_schemas import Token
router = APIRouter(tags=["Login"]) router = APIRouter(tags=["Dashboard", "Login"])
@router.post("/login/access-token") @router.post("/login/access-token", include_in_schema=settings.is_local_environment, response_model=Token)
def login_access_token( def login_access_token(
session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()] session: SessionDep, form_data: LoginDep
) -> Token: ) -> Token:
""" """
OAuth2 compatible token login, get an access token for future requests OAuth2 compatible token login for the dashboard.
This endpoint generates an access token required for authenticating future
requests to the dashboard section of the application. The token is valid for
a predefined expiration period.
- **username**: User's email
- **password**: User's password
**Note:** This login is restricted to dashboard access only and cannot be
used for tenant accounts access to shops
""" """
user = None user = None
user = user_crud.authenticate( user = user_crud.authenticate(

View File

@ -1,5 +1,3 @@
from typing import Annotated
from fastapi import APIRouter from fastapi import APIRouter
from app.api.routes.shop import shop_login_routes, shop_user_routes from app.api.routes.shop import shop_login_routes, shop_user_routes

View File

@ -9,7 +9,8 @@ from app.database.models.user_model import UserRole
from app.schemas.user_schemas import UserRegister from app.schemas.user_schemas import UserRegister
router = APIRouter( router = APIRouter(
prefix="/user" prefix="/user",
tags=["User"]
) )

View File

@ -0,0 +1,18 @@
from fastapi import APIRouter
from app.api.dependencies import CurrentOwnerUser, SessionDep
from app.schemas.shop_schemas import ShopCreate
from app.schemas.user_schemas import Token
router = APIRouter(prefix="/shop", tags=["Shop"])
@router.post("")
def register_new_shop(
session: SessionDep,
current_user: CurrentOwnerUser,
shop_data: ShopCreate
) -> Token:
"""
OAuth2 compatible token login, get an access token for future requests
"""

View File

@ -1,23 +1,29 @@
import logging import logging
from fastapi import APIRouter
from sqlmodel import select from sqlmodel import select
from fastapi import APIRouter
from app.api.dependencies import SessionDep from app.api.dependencies import SessionDep
from app.core.config import settings from app.core.config import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/utils", tags=["utils"]) router = APIRouter(prefix="/utils", tags=["Utils"])
@router.get("/health-check/") @router.get("/health-check/")
async def health_check() -> bool: async def health_check() -> bool:
"""
Ping the API whether it's alive or not
"""
return True return True
@router.get("/test-db/", include_in_schema=settings.is_local_environment) @router.get("/test-db/", include_in_schema=settings.is_local_environment)
async def test_db(session: SessionDep) -> bool: async def test_db(session: SessionDep) -> bool:
"""
Ping database using select 1 to see if connection works
"""
try: try:
session.exec(select(1)) session.exec(select(1))
return True return True

View File

@ -1,11 +1,30 @@
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID, uuid4
from sqlmodel import Session, select from sqlmodel import Session, select
from app.database.models.shop_model import Shop from app.database.models.shop_model import Shop, ShopStatus
from app.database.models.user_model import User
from app.schemas.shop_schemas import ShopCreate
def get_shop_id_from_uuid(session: Session, shop_id: int) -> Optional[UUID]: def get_shop_id_from_uuid(session: Session, shop_id: int) -> Optional[UUID]:
stmt = select(Shop).where(Shop.id == shop_id) stmt = select(Shop).where(Shop.id == shop_id)
db_shop = session.exec(stmt).one_or_none() db_shop = session.exec(stmt).one_or_none()
return db_shop return db_shop
def create_shop(session: Session, shop_data: ShopCreate, creator: User) -> None:
shop_uuid = uuid4()
new_shop = Shop(
uuid=shop_uuid,
owner_id=creator.id,
name=shop_data.name,
description=shop_data.description,
status=ShopStatus.INACTIVE,
contact_email=creator.email,
phone_number=creator.phone_number,
currency=shop_data.currency
)
session.add(new_shop)
session.commit()

View File

@ -48,7 +48,7 @@ class Shop(SQLModel, table=True):
logo: Optional[str] = Field(max_length=100) logo: Optional[str] = Field(max_length=100)
contact_email: str = Field(max_length=128, nullable=False, unique=True) contact_email: str = Field(max_length=128, nullable=False, unique=True)
phone_number: str = Field(max_length=15, nullable=False) phone_number: str = Field(max_length=15, nullable=False)
address: str = Field(nullable=False) address: str = Field(nullable=True)
currency: str = Field(max_length=3, nullable=False) currency: str = Field(max_length=3, nullable=False)
business_hours: ShopBusinessHours = Field( business_hours: ShopBusinessHours = Field(

View File

@ -8,6 +8,7 @@ from app.utils import logger
logger.setup_logger() logger.setup_logger()
def custom_generate_unique_id(route: APIRoute) -> str: def custom_generate_unique_id(route: APIRoute) -> str:
return f"{route.tags[0]}-{route.name}" return f"{route.tags[0]}-{route.name}"
@ -15,7 +16,12 @@ def custom_generate_unique_id(route: APIRoute) -> str:
app = FastAPI( app = FastAPI(
title="SWAG Shop", title="SWAG Shop",
version="0.0.1", version="0.0.1",
generate_unique_id_function=custom_generate_unique_id generate_unique_id_function=custom_generate_unique_id,
openapi_tags=[
{"name": "Dashboard", "description": "Operations endpoints related the admin dashboard. Hidden in production"},
{"name": "Shop", "description": "Shop endpoints which include also user, product and other entity management"},
{"name": "Utils"},
]
) )

View File

@ -0,0 +1,8 @@
from sqlmodel import Field, SQLModel
class ShopCreate(SQLModel):
name: str = Field(max_length=100, nullable=False, unique=True)
description: str = Field(max_length=500, nullable=False)
currency: str = Field(max_length=3, nullable=False)

View File

@ -1,4 +1,4 @@
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/useToast";
import { import {
Toast, Toast,
ToastClose, ToastClose,

View File

@ -0,0 +1,77 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useNavigate } from "@tanstack/react-router"
import { useState } from "react"
import {
type BodyLoginLoginAccessToken as AccessToken,
type ApiError,
LoginService,
type UserPublic,
type UserRegister,
UsersService,
} from "@/client"
import { handleError } from "@/utils"
const isLoggedIn = () => {
return localStorage.getItem("access_token") !== null
}
const useAuth = () => {
const [error, setError] = useState<string | null>(null)
const navigate = useNavigate()
const queryClient = useQueryClient()
const { data: user } = useQuery<UserPublic | null, Error>({
queryKey: ["currentUser"],
queryFn: UsersService.readUserMe,
enabled: isLoggedIn(),
})
const signUpMutation = useMutation({
mutationFn: (data: UserRegister) =>
UsersService.registerUser({ requestBody: data }),
onSuccess: () => {
navigate({ to: "/login" })
},
onError: (err: ApiError) => {
handleError(err)
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] })
},
})
const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({
formData: data,
})
localStorage.setItem("access_token", response.access_token)
}
const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => {
navigate({ to: "/" })
},
onError: (err: ApiError) => {
handleError(err)
},
})
const logout = () => {
localStorage.removeItem("access_token")
navigate({ to: "/login" })
}
return {
signUpMutation,
loginMutation,
logout,
user,
error,
resetError: () => setError(null),
}
}
export { isLoggedIn }
export default useAuth

View File

@ -9,7 +9,7 @@ import {
import { RouterProvider, createRouter } from "@tanstack/react-router"; import { RouterProvider, createRouter } from "@tanstack/react-router";
import { useAuthStore } from "@/stores/authStore"; import { useAuthStore } from "@/stores/authStore";
import { handleServerError } from "@/utils/handle-server-error"; import { handleServerError } from "@/utils/handle-server-error";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { FontProvider } from "./context/font-context"; import { FontProvider } from "./context/font-context";
import { ThemeProvider } from "./context/theme-context"; import { ThemeProvider } from "./context/theme-context";
import "./index.css"; import "./index.css";

View File

@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Form, Form,

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { IconCheck, IconX } from "@tabler/icons-react"; import { IconCheck, IconX } from "@tabler/icons-react";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {

View File

@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { import {

View File

@ -6,7 +6,7 @@ import { fonts } from "@/config/fonts";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useFont } from "@/context/font-context"; import { useFont } from "@/context/font-context";
import { useTheme } from "@/context/theme-context"; import { useTheme } from "@/context/theme-context";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button, buttonVariants } from "@/components/ui/button"; import { Button, buttonVariants } from "@/components/ui/button";
import { import {
Form, Form,

View File

@ -1,7 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { import {

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { import {

View File

@ -3,7 +3,7 @@ import { useFieldArray, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Form, Form,

View File

@ -1,4 +1,4 @@
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { ConfirmDialog } from "@/components/confirm-dialog"; import { ConfirmDialog } from "@/components/confirm-dialog";
import { useTasks } from "../context/tasks-context"; import { useTasks } from "../context/tasks-context";
import { TasksImportDialog } from "./tasks-import-dialog"; import { TasksImportDialog } from "./tasks-import-dialog";

View File

@ -1,7 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,

View File

@ -1,7 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Form, Form,

View File

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import useDialogState from "@/hooks/use-dialog-state"; import useDialogState from "@/hooks/useDialogState";
import { Task } from "../data/schema"; import { Task } from "../data/schema";
type TasksDialogType = "create" | "update" | "delete" | "import"; type TasksDialogType = "create" | "update" | "delete" | "import";

View File

@ -3,7 +3,7 @@
import { z } from "zod"; import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,

View File

@ -2,7 +2,7 @@
import { useState } from "react"; import { useState } from "react";
import { IconAlertTriangle } from "@tabler/icons-react"; import { IconAlertTriangle } from "@tabler/icons-react";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { IconMailPlus, IconSend } from "@tabler/icons-react"; import { IconMailPlus, IconSend } from "@tabler/icons-react";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,

View File

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import useDialogState from "@/hooks/use-dialog-state"; import useDialogState from "@/hooks/useDialogState";
import { User } from "../data/schema"; import { User } from "../data/schema";
type UsersDialogType = "invite" | "add" | "edit" | "delete"; type UsersDialogType = "invite" | "add" | "edit" | "delete";

View File

@ -1,5 +1,5 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/useToast";
export function handleServerError(error: unknown) { export function handleServerError(error: unknown) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console