mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: products crud
This commit is contained in:
parent
7d81afc396
commit
4c8cb42978
12 changed files with 677 additions and 39 deletions
|
|
@ -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
|
||||
14
apps/api/src/db/payments/payments_courses.py
Normal file
14
apps/api/src/db/payments/payments_courses.py
Normal 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())
|
||||
38
apps/api/src/db/payments/payments_products.py
Normal file
38
apps/api/src/db/payments/payments_products.py
Normal 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
|
||||
19
apps/api/src/db/payments/payments_users.py
Normal file
19
apps/api/src/db/payments/payments_users.py
Normal 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())
|
||||
|
|
@ -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"}
|
||||
|
|
|
|||
153
apps/api/src/services/payments/payments_products.py
Normal file
153
apps/api/src/services/payments/payments_products.py
Normal 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]
|
||||
0
apps/api/src/services/payments/products.py
Normal file
0
apps/api/src/services/payments/products.py
Normal file
Loading…
Add table
Add a link
Reference in a new issue