From 802efc482a8547e737c350b24633c2d65aa933b0 Mon Sep 17 00:00:00 2001 From: duyanhehe <duyanhex@gmail.com> Date: Sat, 8 Mar 2025 23:48:59 +0700 Subject: [PATCH] CRUD for shop --- .env.example | 1 + app/backend/main.py | 3 +- app/backend/routes/auth.py | 43 +++++++++++-- app/backend/routes/shop.py | 102 ++++++++++++++++++++++++++++++ app/backend/schemas/shop.py | 25 ++++++++ app/core/config.py | 1 + app/core/security.py | 33 ++++++++++ app/static/default_shop_image.png | Bin 0 -> 16783 bytes 8 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 app/backend/routes/shop.py create mode 100644 app/backend/schemas/shop.py create mode 100644 app/core/security.py create mode 100644 app/static/default_shop_image.png diff --git a/.env.example b/.env.example index 5b74985..9449d42 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,5 @@ DATABASE_PASSWORD="your_mysql_password" DATABASE_HOST="127.0.0.1" DATABASE_NAME="shopping" SECRET_KEY="your_secret_key" +ALGORITHM=HS256 DEBUG=True diff --git a/app/backend/main.py b/app/backend/main.py index d03cae7..34d7e88 100644 --- a/app/backend/main.py +++ b/app/backend/main.py @@ -4,7 +4,7 @@ import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from fastapi import FastAPI -from backend.routes import auth +from backend.routes import auth, shop from backend.database import init_db from core.config import settings @@ -15,6 +15,7 @@ init_db() # Include API routes app.include_router(auth.router, prefix="/auth", tags=["auth"]) +app.include_router(shop.router, prefix="/shops", tags=["shops"]) @app.get("/") diff --git a/app/backend/routes/auth.py b/app/backend/routes/auth.py index c073e7c..3b68f08 100644 --- a/app/backend/routes/auth.py +++ b/app/backend/routes/auth.py @@ -1,12 +1,28 @@ from fastapi import APIRouter, Depends, HTTPException +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from backend.models.models import User from backend.schemas.user import UserCreate, UserLogin from backend.database import get_session from sqlmodel import Session, select from backend.utils.hashing import hash_password, verify_password +from app.core.security import decode_token, create_access_token router = APIRouter() +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") + + +def get_current_user( + token: str = Depends(oauth2_scheme), session: Session = Depends(get_session) +) -> User: + user_id = decode_token(token) + user = session.get(User, user_id) + if not user: + raise HTTPException( + status_code=401, detail="Invalid authentication credentials" + ) + return user + @router.post("/signup") def signup(user_data: UserCreate, session: Session = Depends(get_session)): @@ -29,9 +45,28 @@ def signup(user_data: UserCreate, session: Session = Depends(get_session)): return {"message": "User created successfully"} +# @router.post("/login") +# def login(user_data: UserLogin, session: Session = Depends(get_session)): +# user = session.exec(select(User).where(User.email == user_data.email)).first() +# if not user or not verify_password(user_data.password, user.password): +# raise HTTPException(status_code=401, detail="Invalid credentials") +# access_token = create_access_token(data={"sub": str(user.id)}) +# return { +# "message": "Login successful", +# "user_id": user.id, +# "access_token": access_token, +# } + + @router.post("/login") -def login(user_data: UserLogin, session: Session = Depends(get_session)): - user = session.exec(select(User).where(User.email == user_data.email)).first() - if not user or not verify_password(user_data.password, user.password): +def login( + form_data: OAuth2PasswordRequestForm = Depends(), + session: Session = Depends(get_session), +): + user = session.exec(select(User).where(User.email == form_data.username)).first() + if not user or not verify_password(form_data.password, user.password): raise HTTPException(status_code=401, detail="Invalid credentials") - return {"message": "Login successful", "user_id": user.id} + + access_token = create_access_token(data={"sub": str(user.id)}) + + return {"access_token": access_token, "token_type": "bearer"} diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py new file mode 100644 index 0000000..7775a1c --- /dev/null +++ b/app/backend/routes/shop.py @@ -0,0 +1,102 @@ +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form +from sqlmodel import Session +from backend.models.models import Shop, User +from backend.schemas.shop import ShopCreate, ShopRead +from backend.database import get_session +from backend.routes.auth import get_current_user +import shutil +import os + +router = APIRouter() + +static_dir = os.path.join("app", "static") +os.makedirs(static_dir, exist_ok=True) + + +@router.post("/", response_model=ShopRead) +def create_shop( + name: str = Form(...), + description: str = Form(None), + file: UploadFile = File(None), + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): + shop = ShopCreate(name=name, description=description, owner_id=current_user.id) + db_shop = Shop.from_orm(shop) + + if file and file.filename: + # Save the image to the static directory + file_location = os.path.join(static_dir, f"{db_shop.name}_{file.filename}") + with open(file_location, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + db_shop.image_url = file_location + else: + # Set a default image URL if no file is uploaded + db_shop.image_url = os.path.join(static_dir, "default_shop_image.png") + + session.add(db_shop) + session.commit() + session.refresh(db_shop) + + if file: + # Delete the image file after session commit + os.remove(file_location) + return db_shop + + +@router.get("/{shop_id}", response_model=ShopRead) +def read_shop(shop_id: int, session: Session = Depends(get_session)): + shop = session.get(Shop, shop_id) + if not shop: + raise HTTPException(status_code=404, detail="Shop not found") + return shop + + +@router.put("/{shop_id}", response_model=ShopRead) +def update_shop( + shop_id: int, + name: str = Form(None), + description: str = Form(None), + file: UploadFile = File(None), + session: Session = Depends(get_session), +): + db_shop = session.get(Shop, shop_id) + if not db_shop: + raise HTTPException(status_code=404, detail="Shop not found") + + if name: + db_shop.name = name + if description: + db_shop.description = description + + if file and file.filename: + # Save the image to the static directory + file_location = os.path.join(static_dir, f"{db_shop.name}_{file.filename}") + with open(file_location, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + db_shop.image_url = file_location + else: + # Set a default image URL if no file is uploaded + db_shop.image_url = os.path.join(static_dir, "default_shop_image.png") + + session.add(db_shop) + session.commit() + session.refresh(db_shop) + + if file: + # Delete the image file after session commit + os.remove(file_location) + + return db_shop + + +@router.delete("/{shop_id}") +def delete_shop(shop_id: int, session: Session = Depends(get_session)): + shop = session.get(Shop, shop_id) + if not shop: + raise HTTPException(status_code=404, detail="Shop not found") + session.delete(shop) + session.commit() + return {"message": "Shop deleted successfully"} diff --git a/app/backend/schemas/shop.py b/app/backend/schemas/shop.py new file mode 100644 index 0000000..71763bb --- /dev/null +++ b/app/backend/schemas/shop.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel +from typing import Optional + + +class ShopBase(BaseModel): + name: str + description: Optional[str] = None + + +class ShopCreate(ShopBase): + owner_id: int + + +class ShopRead(ShopBase): + id: int + owner_id: int + image_url: Optional[str] = None + + class Config: + orm_mode = True + + +class ShopUpdate(ShopBase): + name: Optional[str] = None + description: Optional[str] = None diff --git a/app/core/config.py b/app/core/config.py index 6aed5e9..9666c8a 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -9,6 +9,7 @@ class Settings(BaseSettings): database_host: str database_name: str secret_key: str + algorithm: str debug: bool = True @property diff --git a/app/core/security.py b/app/core/security.py new file mode 100644 index 0000000..411ea60 --- /dev/null +++ b/app/core/security.py @@ -0,0 +1,33 @@ +import jwt +from datetime import datetime, timedelta +from fastapi import HTTPException +from jwt import PyJWTError +from core.config import settings + +SECRET_KEY = settings.secret_key +ALGORITHM = settings.algorithm +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + + +def create_access_token(data: dict): + to_encode = data.copy() + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + + +def decode_token(token: str) -> int: + try: + token = token.replace("Bearer ", "") # Remove "Bearer " prefix + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + user_id: int = payload.get("sub") + if user_id is None: + raise HTTPException( + status_code=401, detail="Invalid authentication credentials" + ) + return user_id + except PyJWTError: + raise HTTPException( + status_code=401, detail="Invalid authentication credentials" + ) diff --git a/app/static/default_shop_image.png b/app/static/default_shop_image.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe892f8fcae34a8022b471f94c9ef8b7140d7d6 GIT binary patch literal 16783 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 literal 0 HcmV?d00001 -- GitLab