From 89e07fcec05f5db5cae916e2791df3a866340e27 Mon Sep 17 00:00:00 2001 From: Mykry Date: Mon, 23 Dec 2024 18:36:05 +0500 Subject: [PATCH 1/3] payments --- backend/main.py | 5 ++- backend/models/payment_method.py | 13 ++++++ backend/routes/payment.py | 71 +++++++++++++++++++++++++++++++ backend/schemas.py | 21 +++++++++ backend/tests/test_03_goods.py | 6 --- backend/tests/test_04_payments.py | 55 ++++++++++++++++++++++++ backend/validators.py | 1 - 7 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 backend/models/payment_method.py create mode 100644 backend/routes/payment.py create mode 100644 backend/tests/test_04_payments.py diff --git a/backend/main.py b/backend/main.py index 340e96e..2ed7347 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,7 +2,7 @@ from database import create_tables from fastapi import FastAPI -from routes import good, good_category, login +from routes import good, good_category, login, payment @asynccontextmanager @@ -23,10 +23,11 @@ async def lifespan(_: FastAPI): app.include_router(good_category.router, prefix="/api/v1/good-categories", tags=["Категории товаров"]) app.include_router(good.router, prefix="/api/v1/goods", tags=["Товары"]) - +app.include_router(payment.router, prefix="/api/v1/payments", tags=["Методы оплаты"]) app.include_router(login.router, prefix="/api/v1/auth", tags=["Авторизация"]) + @app.get("/") async def root(): return {"detail": "Welcome to the API! Go to /docs to see the documentation."} diff --git a/backend/models/payment_method.py b/backend/models/payment_method.py new file mode 100644 index 0000000..bcdd775 --- /dev/null +++ b/backend/models/payment_method.py @@ -0,0 +1,13 @@ +# app/models/category.py +from database import Base +from sqlalchemy import Column, Integer, String, Text, LargeBinary + + +class PaymentMethods(Base): + __tablename__ = "payment_methods" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String(255), nullable=False) + description = Column(Text, nullable=True) + image = Column(String, nullable=True) + diff --git a/backend/routes/payment.py b/backend/routes/payment.py new file mode 100644 index 0000000..467cd71 --- /dev/null +++ b/backend/routes/payment.py @@ -0,0 +1,71 @@ +from utils.auth import verify_token +from database import get_db +from fastapi import APIRouter, Depends, HTTPException +from models.payment_method import PaymentMethods +from schemas import PaymentMethodModel, PaymentMethodCreate +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +router = APIRouter() + +@router.get("") +async def get_payments(db: AsyncSession = Depends(get_db)): + """Получение списка методов оплаты""" + payments = (await db.execute(select(PaymentMethods))).scalars().all() + + return { + "items": [PaymentMethodModel.model_validate(payment) for payment in payments], + } + + +@router.post("", response_model=PaymentMethodModel) +async def create_payment(payment: PaymentMethodCreate, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Добавление метода оплаты""" + + db_payment = PaymentMethods(**payment.model_dump()) + db.add(db_payment) + await db.commit() + await db.refresh(db_payment) + + return PaymentMethodModel.model_validate(db_payment) + + +@router.get("/{payment_id}", response_model=PaymentMethodModel) +async def get_payment(payment_id: int, db: AsyncSession = Depends(get_db)): + """Получение метода оплаты по идентификатору""" + payment = await db.get(PaymentMethods, payment_id) + if payment is None: + raise HTTPException(status_code=404, detail="Payment method not found") + + return PaymentMethodModel.model_validate(payment) + + +@router.patch("/{payment_id}", response_model=PaymentMethodModel) +async def update_payment(payment_id: int, payment: PaymentMethodCreate, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Обновление товара""" + db_payment = await db.get(PaymentMethods, payment_id) + if db_payment is None: + raise HTTPException(status_code=404, detail="Payment method not found") + + for key, value in payment.model_dump().items(): + setattr(db_payment, key, value) + + await db.commit() + await db.refresh(db_payment) + + return PaymentMethodModel.model_validate(db_payment) + + +@router.delete("/{payment_id}") +async def delete_good(payment_id: int, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Удаление товара""" + db_payment = await db.get(PaymentMethods, payment_id) + if db_payment is None: + raise HTTPException(status_code=404, detail="Payment method not found") + + await db.delete(db_payment) + await db.commit() + return {"detail": "Payment method deleted"} diff --git a/backend/schemas.py b/backend/schemas.py index 0220f97..80e9298 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -64,3 +64,24 @@ class UserRead(BaseModel): class Config: orm_mode = True + + +class PaymentMethodCreate(BaseModel): + title: str + description: Optional[str] = None + image: Optional[str] = None + + class Config: + orm_mode = True + from_attributes = True + + +class PaymentMethodModel(PaymentMethodCreate): + id: int + title: str + description: Optional[str] = None + image: Optional[str] = None + + class Config: + orm_mode = True + from_attributes = True diff --git a/backend/tests/test_03_goods.py b/backend/tests/test_03_goods.py index 42eab74..1df4f4a 100644 --- a/backend/tests/test_03_goods.py +++ b/backend/tests/test_03_goods.py @@ -1,5 +1,4 @@ import pytest -import os from constants import GOOD_PAGE_SIZE as PAGE_SIZE token = None @@ -154,8 +153,3 @@ async def test_good_pages(client): response = client.get("/goods?page=-1") assert response.status_code == 422 - # delete token file - os.remove("tests/token.txt") - # delete db file - os.remove("test_database.db") - diff --git a/backend/tests/test_04_payments.py b/backend/tests/test_04_payments.py new file mode 100644 index 0000000..b466daa --- /dev/null +++ b/backend/tests/test_04_payments.py @@ -0,0 +1,55 @@ +import pytest +import os + +token = None + +@pytest.mark.asyncio +async def test_payments(client): + global token + with open("tests/token.txt", "r") as file: + token = file.read() + + response = client.post( + "/payments", + json={"title": "Test Pay", "description": "Test Description"}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["title"] == "Test Pay" + assert data["description"] == "Test Description" + +@pytest.mark.asyncio +async def test_get_payment_by_id(client): + response = client.get("/payments/1") + assert response.status_code == 200 + data = response.json() + assert data["title"] == "Test Pay" + assert data["description"] == "Test Description" + +@pytest.mark.asyncio +async def test_edit_payment_by_id(client): + response = client.patch( + "/payments/1", + json={"title": "Updated Pay", "description": "Updated Description"}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["title"] == "Updated Pay" + assert data["description"] == "Updated Description" + +@pytest.mark.asyncio +async def test_delete_payment_by_id(client): + response = client.delete( + "/payments/1", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + response = client.get("/payments/1") + assert response.status_code == 404 + + # delete token file + os.remove("tests/token.txt") + # delete db file + os.remove("test_database.db") \ No newline at end of file diff --git a/backend/validators.py b/backend/validators.py index a3d3b50..5a59791 100644 --- a/backend/validators.py +++ b/backend/validators.py @@ -43,4 +43,3 @@ async def validate_category_exists(category_id: int, db: AsyncSession = Depends( category = result.scalar_one_or_none() if not category: raise HTTPException(status_code=404, detail="Category not found") - From e1406acf77ae20ca473bd60a9045572fca352086 Mon Sep 17 00:00:00 2001 From: Mykry Date: Tue, 24 Dec 2024 15:49:56 +0500 Subject: [PATCH 2/3] auth & login --- backend/models/basket.py | 0 backend/models/checkout.py | 0 backend/models/delivery.py | 0 .../models/{payment_method.py => payment.py} | 1 - backend/models/recipient.py | 16 ++++ backend/models/transaction.py | 0 backend/routes/login.py | 33 ++++++-- backend/routes/payment.py | 2 +- backend/routes/recipient.py | 77 +++++++++++++++++++ backend/schemas.py | 44 +++++++---- backend/utils/auth.py | 1 + 11 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 backend/models/basket.py create mode 100644 backend/models/checkout.py create mode 100644 backend/models/delivery.py rename backend/models/{payment_method.py => payment.py} (93%) create mode 100644 backend/models/recipient.py create mode 100644 backend/models/transaction.py create mode 100644 backend/routes/recipient.py diff --git a/backend/models/basket.py b/backend/models/basket.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/models/checkout.py b/backend/models/checkout.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/models/delivery.py b/backend/models/delivery.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/models/payment_method.py b/backend/models/payment.py similarity index 93% rename from backend/models/payment_method.py rename to backend/models/payment.py index bcdd775..e1249cc 100644 --- a/backend/models/payment_method.py +++ b/backend/models/payment.py @@ -1,4 +1,3 @@ -# app/models/category.py from database import Base from sqlalchemy import Column, Integer, String, Text, LargeBinary diff --git a/backend/models/recipient.py b/backend/models/recipient.py new file mode 100644 index 0000000..6f32cb4 --- /dev/null +++ b/backend/models/recipient.py @@ -0,0 +1,16 @@ +from database import Base +from sqlalchemy import Column, Integer, String, Text, ForeignKey + + +class Recipients(Base): + __tablename__ = "recipients" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE')) + first_name = Column(String(50), nullable=False) + last_name = Column(String(50), nullable=True) + middle_name = Column(String(50), nullable=True) + address = Column(String(50), nullable=False) + zipcode = Column(String(50), nullable=True) + phone = Column(String(50), nullable=False) + email = Column(String(50), nullable=True) diff --git a/backend/models/transaction.py b/backend/models/transaction.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/routes/login.py b/backend/routes/login.py index 2a40de0..86f8958 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -1,34 +1,39 @@ # app/routes/login.py from datetime import datetime, timedelta +from typing import Optional +import jwt +from fastapi.security import HTTPAuthorizationCredentials +from pydantic.v1 import NotNoneError from sqlalchemy import select +from constants import SECRET_KEY, ALGORITHM from database import get_db -from fastapi import APIRouter, Depends, HTTPException, Response -from schemas import UserLogin, UserVerify +from fastapi import APIRouter, Depends, HTTPException, Response, Cookie, Request, Header +from schemas import UserLogin, UserVerify, UserCreate from sqlalchemy.ext.asyncio import AsyncSession from database import is_testing -from models.user import OTP -from utils.auth import send_verification_email, generate_code, generate_access_token, generate_refresh_token +from models.user import OTP, User +from utils.auth import send_verification_email, generate_code, generate_access_token, generate_refresh_token, security router = APIRouter() @router.post("/login") -async def login(user_login: UserLogin, db: AsyncSession = Depends(get_db)): +async def login(user_login: UserLogin, + db: AsyncSession = Depends(get_db)): """Вход пользователя""" otp = generate_code() expiration = datetime.now() + timedelta(minutes=5) - - db_otp = OTP(email=user_login.email, otp=otp, expiration=expiration) + db_otp = OTP(email=str(user_login.email), otp=otp, expiration=expiration) db.add(db_otp) await db.commit() if is_testing: return {"otp": otp} - send_verification_email(user_login.email, "Код для входа в аккаунт", otp) + send_verification_email(str(user_login.email), "Код для входа в аккаунт", otp) return {"message": "OTP sent to your email."} @@ -61,5 +66,17 @@ async def confirm(user_verify: UserVerify, response: Response, db: AsyncSession samesite="strict", # Ограничьте доступ к этому домену ) + result = await db.execute(select(User).filter(User.email == user_verify.email)) + db_user = result.scalar_one_or_none() + + if not db_user: + db_user = User(email=str(user_verify.email), + created_at=datetime.utcnow()) + db.add(db_user) + db_user.last_login = datetime.utcnow() + + await db.commit() + await db.refresh(db_user) + # Возврат access-токена return {"access_token": access_token} diff --git a/backend/routes/payment.py b/backend/routes/payment.py index 467cd71..901d080 100644 --- a/backend/routes/payment.py +++ b/backend/routes/payment.py @@ -1,7 +1,7 @@ from utils.auth import verify_token from database import get_db from fastapi import APIRouter, Depends, HTTPException -from models.payment_method import PaymentMethods +from models.payment import PaymentMethods from schemas import PaymentMethodModel, PaymentMethodCreate from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession diff --git a/backend/routes/recipient.py b/backend/routes/recipient.py new file mode 100644 index 0000000..58d307f --- /dev/null +++ b/backend/routes/recipient.py @@ -0,0 +1,77 @@ +from utils.auth import verify_token +from database import get_db +from fastapi import APIRouter, Depends, HTTPException +from models.recipient import Recipients +from schemas import RecipientModel, RecipientCreate +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +router = APIRouter() + + +@router.get("") +async def get_recipients(db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Получение всех клиентов""" + recipients = (await db.execute(select(Recipients))).scalars().all() + return { + "items": [RecipientModel.model_validate(recipient) for recipient in recipients], + } + + +@router.get("") +async def get_user_recipients(): + pass + + +@router.post("", response_model=RecipientModel) +async def create_payment(payment: RecipientCreate, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Добавление метода оплаты""" + + db_payment = Recipients(**payment.model_dump()) + db.add(db_payment) + await db.commit() + await db.refresh(db_payment) + + return RecipientModel.model_validate(db_payment) + + +@router.get("/{payment_id}", response_model=RecipientModel) +async def get_payment(payment_id: int, db: AsyncSession = Depends(get_db)): + """Получение метода оплаты по идентификатору""" + payment = await db.get(Recipients, payment_id) + if payment is None: + raise HTTPException(status_code=404, detail="Payment method not found") + + return RecipientModel.model_validate(payment) + + +@router.patch("/{payment_id}", response_model=RecipientModel) +async def update_payment(payment_id: int, payment: RecipientCreate, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Обновление товара""" + db_payment = await db.get(Recipients, payment_id) + if db_payment is None: + raise HTTPException(status_code=404, detail="Payment method not found") + + for key, value in payment.model_dump().items(): + setattr(db_payment, key, value) + + await db.commit() + await db.refresh(db_payment) + + return RecipientModel.model_validate(db_payment) + + +@router.delete("/{payment_id}") +async def delete_good(payment_id: int, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): + """Удаление товара""" + db_payment = await db.get(Recipients, payment_id) + if db_payment is None: + raise HTTPException(status_code=404, detail="Payment method not found") + + await db.delete(db_payment) + await db.commit() + return {"detail": "Payment method deleted"} diff --git a/backend/schemas.py b/backend/schemas.py index 80e9298..6f4036d 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -1,7 +1,8 @@ # app/schemas.py +from datetime import datetime from typing import Optional -from pydantic import BaseModel, EmailStr +from pydantic import BaseModel, EmailStr, Field class GoodCategoryCreate(BaseModel): @@ -16,9 +17,6 @@ class Config: class GoodCategoryModel(GoodCategoryCreate): id: int - name: str - description: Optional[str] = None - parent_id: Optional[int] = None class Config: orm_mode = True @@ -38,10 +36,6 @@ class Config: class GoodModel(GoodCreate): id: int - name: str - description: Optional[str] = None - price: int - category_id: Optional[int] = None class Config: orm_mode = True @@ -57,13 +51,15 @@ class UserVerify(BaseModel): otp: str -class UserRead(BaseModel): - id: int +class UserCreate(BaseModel): email: EmailStr - is_verified: bool + created_at: datetime | None = None + last_login: datetime | None = None + role: str | None = Field(default="user") class Config: orm_mode = True + from_attributes = True class PaymentMethodCreate(BaseModel): @@ -78,9 +74,29 @@ class Config: class PaymentMethodModel(PaymentMethodCreate): id: int - title: str - description: Optional[str] = None - image: Optional[str] = None + + class Config: + orm_mode = True + from_attributes = True + + +class RecipientCreate(BaseModel): + user_id: int + first_name: str + last_name: Optional[str] = None + middle_name: Optional[str] = None + address: str + zipcode: Optional[str] = None + phone: str + email: Optional[EmailStr] = None + + class Config: + orm_mode = True + from_attributes = True + + +class RecipientModel(RecipientCreate): + id: int class Config: orm_mode = True diff --git a/backend/utils/auth.py b/backend/utils/auth.py index ebe78e9..e6f88fc 100644 --- a/backend/utils/auth.py +++ b/backend/utils/auth.py @@ -64,3 +64,4 @@ async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(secur raise HTTPException(status_code=401, detail="Token has expired") except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Invalid token") + From 3db1c75f422bc30b3cee674ddab4c467a05c39ff Mon Sep 17 00:00:00 2001 From: Mykry Date: Tue, 24 Dec 2024 16:59:52 +0500 Subject: [PATCH 3/3] recipients & payment --- backend/database.py | 3 +- backend/main.py | 3 +- backend/models/payment.py | 2 +- backend/models/recipient.py | 6 +- backend/models/user.py | 5 +- backend/routes/good.py | 3 +- backend/routes/good_category.py | 2 +- backend/routes/login.py | 18 ++-- backend/routes/payment.py | 4 +- backend/routes/recipient.py | 127 +++++++++++++++++++--------- backend/schemas.py | 16 +++- backend/tests/test_04_payments.py | 8 +- backend/tests/test_05_recipients.py | 120 ++++++++++++++++++++++++++ backend/utils/auth.py | 9 +- 14 files changed, 249 insertions(+), 77 deletions(-) create mode 100644 backend/tests/test_05_recipients.py diff --git a/backend/database.py b/backend/database.py index 1f22ca8..d85e5df 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1,9 +1,8 @@ # app/database.py import sys -from sqlalchemy import event - from constants import DATABASE_URL, TEST_DATABASE_URL +from sqlalchemy import event from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker diff --git a/backend/main.py b/backend/main.py index 2ed7347..ab793ea 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2,7 +2,7 @@ from database import create_tables from fastapi import FastAPI -from routes import good, good_category, login, payment +from routes import good, good_category, login, payment, recipient @asynccontextmanager @@ -24,6 +24,7 @@ async def lifespan(_: FastAPI): app.include_router(good_category.router, prefix="/api/v1/good-categories", tags=["Категории товаров"]) app.include_router(good.router, prefix="/api/v1/goods", tags=["Товары"]) app.include_router(payment.router, prefix="/api/v1/payments", tags=["Методы оплаты"]) +app.include_router(recipient.router, prefix="/api/v1/recipients", tags=["Получатели"]) app.include_router(login.router, prefix="/api/v1/auth", tags=["Авторизация"]) diff --git a/backend/models/payment.py b/backend/models/payment.py index e1249cc..a4ed0c0 100644 --- a/backend/models/payment.py +++ b/backend/models/payment.py @@ -1,5 +1,5 @@ from database import Base -from sqlalchemy import Column, Integer, String, Text, LargeBinary +from sqlalchemy import Column, Integer, LargeBinary, String, Text class PaymentMethods(Base): diff --git a/backend/models/recipient.py b/backend/models/recipient.py index 6f32cb4..f9884f8 100644 --- a/backend/models/recipient.py +++ b/backend/models/recipient.py @@ -1,16 +1,16 @@ from database import Base -from sqlalchemy import Column, Integer, String, Text, ForeignKey +from sqlalchemy import Column, ForeignKey, Integer, String, Text class Recipients(Base): __tablename__ = "recipients" id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE')) + user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE')) first_name = Column(String(50), nullable=False) last_name = Column(String(50), nullable=True) middle_name = Column(String(50), nullable=True) - address = Column(String(50), nullable=False) + address = Column(String(250), nullable=False) zipcode = Column(String(50), nullable=True) phone = Column(String(50), nullable=False) email = Column(String(50), nullable=True) diff --git a/backend/models/user.py b/backend/models/user.py index 6e949f5..c6bc63d 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -1,9 +1,8 @@ from datetime import datetime +from enum import Enum as PyEnum from database import Base -from sqlalchemy import Column, Integer, String, DateTime, Enum - -from enum import Enum as PyEnum +from sqlalchemy import Column, DateTime, Enum, Integer, String class UserRole(PyEnum): diff --git a/backend/routes/good.py b/backend/routes/good.py index 51cc806..c1ffc25 100644 --- a/backend/routes/good.py +++ b/backend/routes/good.py @@ -1,4 +1,3 @@ -from utils.auth import verify_token from constants import GOOD_PAGE_SIZE as PAGE_SIZE from database import get_db from fastapi import APIRouter, Depends, HTTPException, Query, Request @@ -6,7 +5,7 @@ from schemas import GoodCreate, GoodModel from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession - +from utils.auth import verify_token from validators import validate_category_exists router = APIRouter() diff --git a/backend/routes/good_category.py b/backend/routes/good_category.py index 23c85f8..ed689ac 100644 --- a/backend/routes/good_category.py +++ b/backend/routes/good_category.py @@ -1,5 +1,4 @@ # app/routes/good_category.py -from utils.auth import verify_token from constants import GOOD_CATEGORY_PAGE_SIZE as PAGE_SIZE from database import get_db from fastapi import APIRouter, Depends, HTTPException, Query, Request @@ -7,6 +6,7 @@ from schemas import GoodCategoryCreate, GoodCategoryModel from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession +from utils.auth import verify_token from validators import (validate_category_exists, validate_category_name, validate_category_name_update, validate_parent_category, validate_parent_itself) diff --git a/backend/routes/login.py b/backend/routes/login.py index 86f8958..fd26322 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -3,19 +3,19 @@ from typing import Optional import jwt +from constants import ALGORITHM, SECRET_KEY +from database import get_db, is_testing +from fastapi import (APIRouter, Cookie, Depends, Header, HTTPException, + Request, Response) from fastapi.security import HTTPAuthorizationCredentials +from models.user import OTP, User from pydantic.v1 import NotNoneError +from schemas import UserCreate, UserLogin, UserVerify from sqlalchemy import select - -from constants import SECRET_KEY, ALGORITHM -from database import get_db -from fastapi import APIRouter, Depends, HTTPException, Response, Cookie, Request, Header -from schemas import UserLogin, UserVerify, UserCreate from sqlalchemy.ext.asyncio import AsyncSession -from database import is_testing - -from models.user import OTP, User -from utils.auth import send_verification_email, generate_code, generate_access_token, generate_refresh_token, security +from utils.auth import (generate_access_token, generate_code, + generate_refresh_token, security, + send_verification_email) router = APIRouter() diff --git a/backend/routes/payment.py b/backend/routes/payment.py index 901d080..62b7805 100644 --- a/backend/routes/payment.py +++ b/backend/routes/payment.py @@ -1,10 +1,10 @@ -from utils.auth import verify_token from database import get_db from fastapi import APIRouter, Depends, HTTPException from models.payment import PaymentMethods -from schemas import PaymentMethodModel, PaymentMethodCreate +from schemas import PaymentMethodCreate, PaymentMethodModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from utils.auth import verify_token router = APIRouter() diff --git a/backend/routes/recipient.py b/backend/routes/recipient.py index 58d307f..01bfaf3 100644 --- a/backend/routes/recipient.py +++ b/backend/routes/recipient.py @@ -1,10 +1,11 @@ -from utils.auth import verify_token from database import get_db from fastapi import APIRouter, Depends, HTTPException from models.recipient import Recipients -from schemas import RecipientModel, RecipientCreate +from models.user import User, UserRole +from schemas import RecipientCreate, RecipientEdit, RecipientModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from utils.auth import verify_token router = APIRouter() @@ -12,66 +13,112 @@ @router.get("") async def get_recipients(db: AsyncSession = Depends(get_db), user_data: dict = Depends(verify_token)): - """Получение всех клиентов""" - recipients = (await db.execute(select(Recipients))).scalars().all() + """Получение всех получателей, если запрос делает админ (или продавец), и только своих, если - пользователь""" + user_email = user_data.get("sub") + result = await db.execute(select(User).filter(User.email == user_email)) + db_user = result.scalars().first() + + if db_user.role in [UserRole.ADMIN, UserRole.SELLER]: + result_recipients = await db.execute(select(Recipients)) + else: + result_recipients = await db.execute( + select(Recipients).filter(Recipients.user_id == db_user.id) + ) + + recipients = result_recipients.scalars().all() + return { "items": [RecipientModel.model_validate(recipient) for recipient in recipients], } -@router.get("") -async def get_user_recipients(): - pass - - @router.post("", response_model=RecipientModel) -async def create_payment(payment: RecipientCreate, db: AsyncSession = Depends(get_db), +async def add_recipients(payment: RecipientCreate, db: AsyncSession = Depends(get_db), user_data: dict = Depends(verify_token)): - """Добавление метода оплаты""" - - db_payment = Recipients(**payment.model_dump()) - db.add(db_payment) + """Добавление получателя""" + user_email = user_data.get("sub") + result = await db.execute(select(User).filter(User.email == user_email)) + db_user = result.scalars().first() + + if db_user.role in [UserRole.ADMIN, UserRole.SELLER]: + user_id = payment.user_id + else: + user_id = db_user.id + + new_recipient = Recipients( + user_id=user_id, + first_name=payment.first_name, + last_name=payment.last_name, + middle_name=payment.middle_name, + address=payment.address, + zipcode=payment.zipcode, + phone=payment.phone, + email=str(payment.email), + ) + + db.add(new_recipient) await db.commit() - await db.refresh(db_payment) + await db.refresh(new_recipient) - return RecipientModel.model_validate(db_payment) + return RecipientModel.model_validate(new_recipient) -@router.get("/{payment_id}", response_model=RecipientModel) -async def get_payment(payment_id: int, db: AsyncSession = Depends(get_db)): +@router.get("/{recipient_id}", response_model=RecipientModel) +async def get_payment(recipient_id: int, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): """Получение метода оплаты по идентификатору""" - payment = await db.get(Recipients, payment_id) - if payment is None: - raise HTTPException(status_code=404, detail="Payment method not found") + recipient = await db.get(Recipients, recipient_id) + if recipient is None: + raise HTTPException(status_code=404, detail="Recipient not found") - return RecipientModel.model_validate(payment) + user_email = user_data.get("sub") + result = await db.execute(select(User).filter(User.email == user_email)) + db_user = result.scalars().first() + if recipient.user_id != db_user.id and db_user.role not in [UserRole.ADMIN, UserRole.SELLER]: + raise HTTPException(status_code=404, detail="Recipient not found") + return RecipientModel.model_validate(recipient) -@router.patch("/{payment_id}", response_model=RecipientModel) -async def update_payment(payment_id: int, payment: RecipientCreate, db: AsyncSession = Depends(get_db), - user_data: dict = Depends(verify_token)): + +@router.patch("/{recipient_id}", response_model=RecipientModel) +async def update_recipient(recipient_id: int, recipient: RecipientEdit, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): """Обновление товара""" - db_payment = await db.get(Recipients, payment_id) - if db_payment is None: - raise HTTPException(status_code=404, detail="Payment method not found") + db_recipient = await db.get(Recipients, recipient_id) + if db_recipient is None: + raise HTTPException(status_code=404, detail="Recipient not found") + + user_email = user_data.get("sub") + result = await db.execute(select(User).filter(User.email == user_email)) + db_user = result.scalars().first() - for key, value in payment.model_dump().items(): - setattr(db_payment, key, value) + if db_recipient.user_id != db_user.id and db_user.role not in [UserRole.ADMIN, UserRole.SELLER]: + raise HTTPException(status_code=404, detail="Recipient not found") + + for key, value in recipient.model_dump().items(): + setattr(db_recipient, key, value) await db.commit() - await db.refresh(db_payment) + await db.refresh(db_recipient) - return RecipientModel.model_validate(db_payment) + return RecipientModel.model_validate(db_recipient) -@router.delete("/{payment_id}") -async def delete_good(payment_id: int, db: AsyncSession = Depends(get_db), - user_data: dict = Depends(verify_token)): +@router.delete("/{recipient_id}") +async def delete_recipient(recipient_id: int, db: AsyncSession = Depends(get_db), + user_data: dict = Depends(verify_token)): """Удаление товара""" - db_payment = await db.get(Recipients, payment_id) - if db_payment is None: - raise HTTPException(status_code=404, detail="Payment method not found") + db_recipient = await db.get(Recipients, recipient_id) + if db_recipient is None: + raise HTTPException(status_code=404, detail="Recipient not found") + + user_email = user_data.get("sub") + result = await db.execute(select(User).filter(User.email == user_email)) + db_user = result.scalars().first() + + if db_recipient.user_id != db_user.id and db_user.role not in [UserRole.ADMIN, UserRole.SELLER]: + raise HTTPException(status_code=404, detail="Recipient not found") - await db.delete(db_payment) + await db.delete(db_recipient) await db.commit() - return {"detail": "Payment method deleted"} + return {"detail": "Recipient deleted"} diff --git a/backend/schemas.py b/backend/schemas.py index 6f4036d..69dfef6 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -80,7 +80,21 @@ class Config: from_attributes = True -class RecipientCreate(BaseModel): +class RecipientEdit(BaseModel): + first_name: str + last_name: Optional[str] = None + middle_name: Optional[str] = None + address: str + zipcode: Optional[str] = None + phone: str + email: Optional[EmailStr] = None + + class Config: + orm_mode = True + from_attributes = True + + +class RecipientCreate(RecipientEdit): user_id: int first_name: str last_name: Optional[str] = None diff --git a/backend/tests/test_04_payments.py b/backend/tests/test_04_payments.py index b466daa..afbb6b7 100644 --- a/backend/tests/test_04_payments.py +++ b/backend/tests/test_04_payments.py @@ -1,5 +1,4 @@ import pytest -import os token = None @@ -47,9 +46,4 @@ async def test_delete_payment_by_id(client): ) assert response.status_code == 200 response = client.get("/payments/1") - assert response.status_code == 404 - - # delete token file - os.remove("tests/token.txt") - # delete db file - os.remove("test_database.db") \ No newline at end of file + assert response.status_code == 404 \ No newline at end of file diff --git a/backend/tests/test_05_recipients.py b/backend/tests/test_05_recipients.py new file mode 100644 index 0000000..6730e0a --- /dev/null +++ b/backend/tests/test_05_recipients.py @@ -0,0 +1,120 @@ +import os + +import pytest + +token = None + +@pytest.mark.asyncio +async def test_recipients(client): + global token + with open("tests/token.txt", "r") as file: + token = file.read() + + response = client.get( + "/recipients", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + data = response.json() + assert isinstance(data["items"], list) + +@pytest.mark.asyncio +async def test_add_recipient(client): + new_recipient = { + "first_name": "John", + "last_name": "Doe", + "middle_name": "Smith", + "address": "123 Main St", + "zipcode": "12345", + "phone": "555-555-5555", + "email": "john.doe@example.com", + "user_id": 0, + } + + response = client.post( + "/recipients", + json=new_recipient, + headers={"Authorization": f"Bearer {token}"}, + ) + + assert response.status_code == 200 + data = response.json() + assert data["first_name"] == new_recipient["first_name"] + assert data["last_name"] == new_recipient["last_name"] + assert data["email"] == new_recipient["email"] + +@pytest.mark.asyncio +async def test_get_recipient_by_id(client): + recipient_id = 1 + recipient = { + "first_name": "John", + "last_name": "Doe", + "middle_name": "Smith", + "address": "123 Main St", + "zipcode": "12345", + "phone": "555-555-5555", + "email": "john.doe@example.com", + } + response = client.get( + f"/recipients/{recipient_id}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == recipient_id + assert data["first_name"] == recipient["first_name"] + assert data["last_name"] == recipient["last_name"] + assert data["email"] == recipient["email"] + assert data["address"] == recipient["address"] + assert data["zipcode"] == recipient["zipcode"] + assert data["phone"] == recipient["phone"] + assert data["email"] == recipient["email"] + +@pytest.mark.asyncio +async def test_update_recipient(client): + recipient_id = 1 + updated_recipient = { + "first_name": "John", + "last_name": "Doe", + "middle_name": "Smith", + "address": "123 Main St", + "zipcode": "12345", + "phone": "666-666-6666", + "email": "john.doe@example.ru", + } + response = client.patch( + f"/recipients/{recipient_id}", + json=updated_recipient, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["first_name"] == updated_recipient["first_name"] + assert data["last_name"] == updated_recipient["last_name"] + assert data["email"] == updated_recipient["email"] + assert data["address"] == updated_recipient["address"] + assert data["zipcode"] == updated_recipient["zipcode"] + assert data["phone"] == updated_recipient["phone"] + assert data["email"] == updated_recipient["email"] + assert data["id"] == recipient_id + +@pytest.mark.asyncio +async def test_delete_recipient(client): + recipient_id = 1 + response = client.delete( + f"/recipients/{recipient_id}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + + # Проверяем, что получатель был удален + response = client.get( + f"/recipients/{recipient_id}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 404 + + # delete token file + os.remove("tests/token.txt") + # delete db file + os.remove("test_database.db") \ No newline at end of file diff --git a/backend/utils/auth.py b/backend/utils/auth.py index e6f88fc..1cae77d 100644 --- a/backend/utils/auth.py +++ b/backend/utils/auth.py @@ -1,13 +1,12 @@ from datetime import datetime, timedelta +from email.mime.text import MIMEText from random import randint from smtplib import SMTP -from email.mime.text import MIMEText -from fastapi import Depends, HTTPException -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials - -from constants import SENDER_EMAIL, SENDER_PASSWORD, SECRET_KEY, ALGORITHM import jwt +from constants import ALGORITHM, SECRET_KEY, SENDER_EMAIL, SENDER_PASSWORD +from fastapi import Depends, HTTPException +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer security = HTTPBearer()