feat: introduced plans pricing

This commit is contained in:
2026-07-25 15:31:44 +07:00
parent 633c79e817
commit 2547bcfcba
10 changed files with 144 additions and 7 deletions

View File

@@ -0,0 +1,41 @@
"""edited pricing + added addons
Revision ID: bb542353b092
Revises: 74bd9cedebdd
Create Date: 2026-07-25 00:35:38.927479
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'bb542353b092'
down_revision: Union[str, Sequence[str], None] = '74bd9cedebdd'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('addons',
sa.Column('id', sa.TEXT(), nullable=False),
sa.Column('name', sa.TEXT(), nullable=False),
sa.Column('price', sa.FLOAT(), nullable=False),
sa.Column('free_threshold', sa.INTEGER(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('id')
)
op.create_unique_constraint(None, 'pricing_configs', ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'pricing_configs', type_='unique')
op.drop_table('addons')
# ### end Alembic commands ###

View File

@@ -1,5 +1,6 @@
from .addons import Addon
from .pricing import PricingConfig
from .sessions import Session
from .users import User
__all__ = ["PricingConfig", "Session", "User"]
__all__ = ["Addon", "PricingConfig", "Session", "User"]

13
db/models/addons.py Normal file
View File

@@ -0,0 +1,13 @@
from sqlalchemy import FLOAT, INTEGER, TEXT
from sqlalchemy.orm import Mapped, mapped_column
from db.base import Base
class Addon(Base):
__tablename__ = "addons"
id: Mapped[str] = mapped_column(TEXT, nullable=False, unique=True, primary_key=True)
name: Mapped[str] = mapped_column(TEXT, nullable=False, unique=False)
price: Mapped[float] = mapped_column(FLOAT, nullable=False)
free_threshold: Mapped[int] = mapped_column(INTEGER, default=-1)

View File

@@ -9,11 +9,13 @@ from db.base import Base
class PricingConfig(Base):
__tablename__ = "pricing_configs"
__table_args__ = (
CheckConstraint("id = 1", name="single_row_check"),
)
__table_args__ = (CheckConstraint("id = 1", name="single_row_check"),)
id: Mapped[int] = mapped_column(INTEGER, autoincrement=True, unique=True, nullable=False, primary_key=True)
id: Mapped[int] = mapped_column(
INTEGER, autoincrement=True, unique=True, nullable=False, primary_key=True
)
device_price: Mapped[float] = mapped_column(FLOAT, nullable=False)
updated_at: Mapped[datetime] = mapped_column(TIMESTAMP, server_default=func.now(), onupdate=func.now())
updated_at: Mapped[datetime] = mapped_column(
TIMESTAMP, server_default=func.now(), onupdate=func.now()
)

15
repositories/addons.py Normal file
View File

@@ -0,0 +1,15 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from db.models import Addon
class AddonsRepository:
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def get_all(self) -> list[Addon]:
stmt = select(Addon)
r = await self.session.execute(stmt)
return list(r.scalars().all())

15
repositories/pricing.py Normal file
View File

@@ -0,0 +1,15 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from db.models.pricing import PricingConfig
class PricingRepository:
def __init__(self, session: AsyncSession) -> None:
self.session = session
async def get(self) -> PricingConfig | None:
stmt = select(PricingConfig).where(PricingConfig.id == 1)
r = await self.session.execute(stmt)
return r.scalar_one_or_none()

View File

@@ -1,5 +1,6 @@
from fastapi import APIRouter
from .auth import router as auth_router
from .plans import router as plans_router
routers: list[APIRouter] = [auth_router]
routers: list[APIRouter] = [auth_router, plans_router]

19
routes/plans.py Normal file
View File

@@ -0,0 +1,19 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from db.session import get_db
from repositories.addons import AddonsRepository
from repositories.pricing import PricingRepository
from schemas.plans import PricingPlans
from services.plans import get_pricing_model
router = APIRouter(prefix="/plans")
@router.get("/", response_model=PricingPlans)
async def get_plans(session: AsyncSession = Depends(get_db)):
addons_repo = AddonsRepository(session)
pricing_repo = PricingRepository(session)
res = await get_pricing_model(addons_repo, pricing_repo)
return res

13
schemas/plans.py Normal file
View File

@@ -0,0 +1,13 @@
from pydantic import BaseModel
class AddonData(BaseModel):
id: str
name: str
price: float
free_threshold: int
class PricingPlans(BaseModel):
device_price: int
addons: list[AddonData]

17
services/plans.py Normal file
View File

@@ -0,0 +1,17 @@
from repositories.addons import AddonsRepository
from repositories.pricing import PricingRepository
from schemas.plans import AddonData, PricingPlans
async def get_pricing_model(addons_repo: AddonsRepository, pricing_repo: PricingRepository):
pricing = await pricing_repo.get()
if not pricing:
return None
addons = await addons_repo.get_all()
addons_data = [
AddonData(id=a.id, name=a.name, price=a.price, free_threshold=a.free_threshold)
for a in addons
]
return PricingPlans(device_price=pricing.device_price, addons=addons_data)