feat: introduced plans pricing
This commit is contained in:
41
alembic/versions/bb542353b092_edited_pricing_added_addons.py
Normal file
41
alembic/versions/bb542353b092_edited_pricing_added_addons.py
Normal 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 ###
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
from .addons import Addon
|
||||||
from .pricing import PricingConfig
|
from .pricing import PricingConfig
|
||||||
from .sessions import Session
|
from .sessions import Session
|
||||||
from .users import User
|
from .users import User
|
||||||
|
|
||||||
__all__ = ["PricingConfig", "Session", "User"]
|
__all__ = ["Addon", "PricingConfig", "Session", "User"]
|
||||||
|
|||||||
13
db/models/addons.py
Normal file
13
db/models/addons.py
Normal 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)
|
||||||
@@ -9,11 +9,13 @@ from db.base import Base
|
|||||||
class PricingConfig(Base):
|
class PricingConfig(Base):
|
||||||
__tablename__ = "pricing_configs"
|
__tablename__ = "pricing_configs"
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (CheckConstraint("id = 1", name="single_row_check"),)
|
||||||
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)
|
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
15
repositories/addons.py
Normal 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
15
repositories/pricing.py
Normal 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()
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from .auth import router as auth_router
|
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
19
routes/plans.py
Normal 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
13
schemas/plans.py
Normal 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
17
services/plans.py
Normal 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)
|
||||||
Reference in New Issue
Block a user