feat: products crud

This commit is contained in:
swve 2024-10-16 00:31:36 +02:00
parent 7d81afc396
commit 4c8cb42978
12 changed files with 677 additions and 39 deletions

View file

@ -1,21 +1,22 @@
from datetime import datetime
from enum import Enum
from typing import Literal, Optional
from typing import Optional
from pydantic import BaseModel
from sqlalchemy import JSON
from sqlmodel import Field, SQLModel, Column, BigInteger, ForeignKey
# Stripe provider config
class StripeProviderConfig(BaseModel):
stripe_key: str = ""
stripe_secret_key: str = ""
stripe_webhook_secret: str = ""
# PaymentsConfig
class PaymentProviderEnum(str, Enum):
STRIPE = "stripe"
class PaymentsConfigBase(SQLModel):
enabled: bool = False
enabled: bool = True
provider: PaymentProviderEnum = PaymentProviderEnum.STRIPE
provider_config: dict = Field(default={}, sa_column=Column(JSON))
@ -34,7 +35,7 @@ class PaymentsConfigCreate(PaymentsConfigBase):
class PaymentsConfigUpdate(PaymentsConfigBase):
enabled: Optional[bool] = False
enabled: Optional[bool] = True
provider_config: Optional[dict] = None
@ -46,4 +47,4 @@ class PaymentsConfigRead(PaymentsConfigBase):
class PaymentsConfigDelete(SQLModel):
id: int
id: int

View file

@ -0,0 +1,14 @@
from enum import Enum
from sqlmodel import SQLModel, Field, Column, BigInteger, ForeignKey, String, JSON
from typing import Optional
from datetime import datetime
class PaymentCourseBase(SQLModel):
course_id: int = Field(sa_column=Column(BigInteger, ForeignKey("course.id", ondelete="CASCADE")))
payment_product_id: int = Field(sa_column=Column(BigInteger, ForeignKey("paymentsproduct.id", ondelete="CASCADE")))
org_id: int = Field(sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE")))
class PaymentCourse(PaymentCourseBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
creation_date: datetime = Field(default=datetime.now())
update_date: datetime = Field(default=datetime.now())

View file

@ -0,0 +1,38 @@
from enum import Enum
from sqlmodel import SQLModel, Field, Column, BigInteger, ForeignKey, String, JSON
from typing import Optional
from datetime import datetime
class PaymentProductTypeEnum(str, Enum):
SUBSCRIPTION = "subscription"
ONE_TIME = "one_time"
class PaymentsProductBase(SQLModel):
name: str = ""
description: Optional[str] = ""
product_type: PaymentProductTypeEnum = PaymentProductTypeEnum.ONE_TIME
benefits: str = ""
amount: float = 0.0
class PaymentsProduct(PaymentsProductBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
org_id: int = Field(
sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE"))
)
payments_config_id: int = Field(sa_column=Column(BigInteger, ForeignKey("paymentsconfig.id", ondelete="CASCADE")))
provider_product_id: str = Field(sa_column=Column(String))
creation_date: datetime = Field(default=datetime.now())
update_date: datetime = Field(default=datetime.now())
class PaymentsProductCreate(PaymentsProductBase):
pass
class PaymentsProductUpdate(PaymentsProductBase):
pass
class PaymentsProductRead(PaymentsProductBase):
id: int
org_id: int
payments_config_id: int
creation_date: datetime
update_date: datetime

View file

@ -0,0 +1,19 @@
from enum import Enum
from sqlmodel import SQLModel, Field, Column, BigInteger, ForeignKey, String, JSON
from typing import Optional
from datetime import datetime
class PaymentUserStatusEnum(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
class PaymentsUserBase(SQLModel):
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("user.id", ondelete="CASCADE")))
status: PaymentUserStatusEnum = PaymentUserStatusEnum.ACTIVE
payment_product_id: int = Field(sa_column=Column(BigInteger, ForeignKey("paymentsproduct.id", ondelete="CASCADE")))
org_id: int = Field(sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE")))
class PaymentsUser(PaymentsUserBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
creation_date: datetime = Field(default=datetime.now())
update_date: datetime = Field(default=datetime.now())

View file

@ -10,6 +10,9 @@ from src.services.payments.payments import (
update_payments_config,
delete_payments_config,
)
from src.db.payments.payments_products import PaymentsProduct, PaymentsProductCreate, PaymentsProductRead, PaymentsProductUpdate
from src.services.payments.payments_products import create_payments_product, delete_payments_product, get_payments_product, list_payments_products, update_payments_product
router = APIRouter()
@ -51,3 +54,54 @@ async def api_delete_payments_config(
):
await delete_payments_config(request, org_id, current_user, db_session)
return {"message": "Payments config deleted successfully"}
@router.post("/{org_id}/products")
async def api_create_payments_product(
request: Request,
org_id: int,
payments_product: PaymentsProductCreate,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
) -> PaymentsProductRead:
return await create_payments_product(request, org_id, payments_product, current_user, db_session)
@router.get("/{org_id}/products")
async def api_get_payments_products(
request: Request,
org_id: int,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
) -> list[PaymentsProductRead]:
return await list_payments_products(request, org_id, current_user, db_session)
@router.get("/{org_id}/products/{product_id}")
async def api_get_payments_product(
request: Request,
org_id: int,
product_id: int,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
) -> PaymentsProductRead:
return await get_payments_product(request, org_id, product_id, current_user, db_session)
@router.put("/{org_id}/products/{product_id}")
async def api_update_payments_product(
request: Request,
org_id: int,
product_id: int,
payments_product: PaymentsProductUpdate,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
) -> PaymentsProductRead:
return await update_payments_product(request, org_id, product_id, payments_product, current_user, db_session)
@router.delete("/{org_id}/products/{product_id}")
async def api_delete_payments_product(
request: Request,
org_id: int,
product_id: int,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
await delete_payments_product(request, org_id, product_id, current_user, db_session)
return {"message": "Payments product deleted successfully"}

View file

@ -0,0 +1,153 @@
from fastapi import HTTPException, Request
from sqlmodel import Session, select
from src.db.payments.payments import PaymentsConfig
from src.db.payments.payments_products import (
PaymentsProduct,
PaymentsProductCreate,
PaymentsProductUpdate,
PaymentsProductRead,
)
from src.db.users import PublicUser, AnonymousUser
from src.db.organizations import Organization
from src.services.orgs.orgs import rbac_check
from datetime import datetime
from uuid import uuid4
async def create_payments_product(
request: Request,
org_id: int,
payments_product: PaymentsProductCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
) -> PaymentsProductRead:
# Check if organization exists
statement = select(Organization).where(Organization.id == org_id)
org = db_session.exec(statement).first()
if not org:
raise HTTPException(status_code=404, detail="Organization not found")
# RBAC check
await rbac_check(request, org.org_uuid, current_user, "create", db_session)
# Check if payments config exists and has a valid id
statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
config = db_session.exec(statement).first()
if not config or config.id is None:
raise HTTPException(status_code=404, detail="Valid payments config not found")
# Create new payments product
new_product = PaymentsProduct(**payments_product.model_dump(), org_id=org_id, payments_config_id=config.id)
new_product.creation_date = datetime.now()
new_product.update_date = datetime.now()
db_session.add(new_product)
db_session.commit()
db_session.refresh(new_product)
return PaymentsProductRead.model_validate(new_product)
async def get_payments_product(
request: Request,
org_id: int,
product_id: int,
current_user: PublicUser | AnonymousUser,
db_session: Session,
) -> PaymentsProductRead:
# Check if organization exists
statement = select(Organization).where(Organization.id == org_id)
org = db_session.exec(statement).first()
if not org:
raise HTTPException(status_code=404, detail="Organization not found")
# RBAC check
await rbac_check(request, org.org_uuid, current_user, "read", db_session)
# Get payments product
statement = select(PaymentsProduct).where(PaymentsProduct.id == product_id, PaymentsProduct.org_id == org_id)
product = db_session.exec(statement).first()
if not product:
raise HTTPException(status_code=404, detail="Payments product not found")
return PaymentsProductRead.model_validate(product)
async def update_payments_product(
request: Request,
org_id: int,
product_id: int,
payments_product: PaymentsProductUpdate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
) -> PaymentsProductRead:
# Check if organization exists
statement = select(Organization).where(Organization.id == org_id)
org = db_session.exec(statement).first()
if not org:
raise HTTPException(status_code=404, detail="Organization not found")
# RBAC check
await rbac_check(request, org.org_uuid, current_user, "update", db_session)
# Get existing payments product
statement = select(PaymentsProduct).where(PaymentsProduct.id == product_id, PaymentsProduct.org_id == org_id)
product = db_session.exec(statement).first()
if not product:
raise HTTPException(status_code=404, detail="Payments product not found")
# Update product
for key, value in payments_product.model_dump().items():
setattr(product, key, value)
product.update_date = datetime.now()
db_session.add(product)
db_session.commit()
db_session.refresh(product)
return PaymentsProductRead.model_validate(product)
async def delete_payments_product(
request: Request,
org_id: int,
product_id: int,
current_user: PublicUser | AnonymousUser,
db_session: Session,
) -> None:
# Check if organization exists
statement = select(Organization).where(Organization.id == org_id)
org = db_session.exec(statement).first()
if not org:
raise HTTPException(status_code=404, detail="Organization not found")
# RBAC check
await rbac_check(request, org.org_uuid, current_user, "delete", db_session)
# Get existing payments product
statement = select(PaymentsProduct).where(PaymentsProduct.id == product_id, PaymentsProduct.org_id == org_id)
product = db_session.exec(statement).first()
if not product:
raise HTTPException(status_code=404, detail="Payments product not found")
# Delete product
db_session.delete(product)
db_session.commit()
async def list_payments_products(
request: Request,
org_id: int,
current_user: PublicUser | AnonymousUser,
db_session: Session,
) -> list[PaymentsProductRead]:
# Check if organization exists
statement = select(Organization).where(Organization.id == org_id)
org = db_session.exec(statement).first()
if not org:
raise HTTPException(status_code=404, detail="Organization not found")
# RBAC check
await rbac_check(request, org.org_uuid, current_user, "read", db_session)
# Get payments products ordered by id
statement = select(PaymentsProduct).where(PaymentsProduct.org_id == org_id).order_by(PaymentsProduct.id.desc())
products = db_session.exec(statement).all()
return [PaymentsProductRead.model_validate(product) for product in products]