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 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()
@ -9,3 +9,4 @@ api_router.include_router(user_routes.router)
api_router.include_router(utils_routes.router)
api_router.include_router(login_routes.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
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from pydantic import ValidationError
from sqlmodel import Session
@ -19,6 +19,7 @@ reusable_oauth2 = OAuth2PasswordBearer(
SessionDep = Annotated[Session, Depends(get_session)]
TokenDep = Annotated[str, Depends(reusable_oauth2)]
LoginDep = Annotated[OAuth2PasswordRequestForm, Depends()]
def get_current_user(session: SessionDep, token: TokenDep) -> User:

View File

@ -1,24 +1,32 @@
from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from fastapi import APIRouter, HTTPException
from app.api.dependencies import SessionDep
from app.api.dependencies import LoginDep, SessionDep
from app.core import security
from app.core.config import settings
from app.crud import user_crud
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(
session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
session: SessionDep, form_data: LoginDep
) -> 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 = user_crud.authenticate(

View File

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

View File

@ -1,11 +1,30 @@
from typing import Optional
from uuid import UUID
from uuid import UUID, uuid4
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]:
stmt = select(Shop).where(Shop.id == shop_id)
db_shop = session.exec(stmt).one_or_none()
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)
contact_email: str = Field(max_length=128, nullable=False, unique=True)
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)
business_hours: ShopBusinessHours = Field(

View File

@ -8,6 +8,7 @@ from app.utils import logger
logger.setup_logger()
def custom_generate_unique_id(route: APIRoute) -> str:
return f"{route.tags[0]}-{route.name}"
@ -15,7 +16,12 @@ def custom_generate_unique_id(route: APIRoute) -> str:
app = FastAPI(
title="SWAG Shop",
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 {
Toast,
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 { useAuthStore } from "@/stores/authStore";
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 { ThemeProvider } from "./context/theme-context";
import "./index.css";

View File

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

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "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 { Button } from "@/components/ui/button";
import {

View File

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

View File

@ -6,7 +6,7 @@ import { fonts } from "@/config/fonts";
import { cn } from "@/lib/utils";
import { useFont } from "@/context/font-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 {
Form,

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { useFieldArray, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Link } from "@tanstack/react-router";
import { cn } from "@/lib/utils";
import { toast } from "@/hooks/use-toast";
import { toast } from "@/hooks/useToast";
import { Button } from "@/components/ui/button";
import {
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 { useTasks } from "../context/tasks-context";
import { TasksImportDialog } from "./tasks-import-dialog";

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
import { useState } from "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 { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
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 {
Dialog,

View File

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

View File

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