fix!: removed .commit() from repository level

This commit is contained in:
2026-08-24 11:07:26 +07:00
parent 8d1b753b99
commit a374501e4d
22 changed files with 177 additions and 180 deletions

View File

@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from core.secrets import (
estimate_password_strength,
@@ -8,7 +7,7 @@ from core.secrets import (
hash_refresh_token,
verify_password,
)
from db.session import get_db
from db.session import UnitOfWork, get_uow
from repositories.sessions import SessionsRepository
from repositories.users import UserRepository
from schemas.login import UserLogin, UserLoginData, UserTokens
@@ -22,8 +21,8 @@ router = APIRouter(prefix="/auth")
@router.post("/signup")
async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db)):
users_repo = UserRepository(session)
async def signup(req: UserRegistration, uow: UnitOfWork = Depends(get_uow)):
users_repo = UserRepository(uow)
if req.provider == "credentials":
if not req.username or not req.password:
@@ -44,6 +43,7 @@ async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db))
user = await users_repo.create(
username=req.username, hashed_password=password_hash, referal_id=referal_id
)
await uow.commit()
return JSONResponse(
UserInfo(
username=user.username,
@@ -57,9 +57,9 @@ async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db))
@router.post("/login", response_model=UserLogin)
async def login(req: UserLoginData, session: AsyncSession = Depends(get_db)):
users_repo = UserRepository(session)
sessions_repo = SessionsRepository(session)
async def login(req: UserLoginData, uow: UnitOfWork = Depends(get_uow)):
users_repo = UserRepository(uow)
sessions_repo = SessionsRepository(uow)
if req.provider == "credentials":
if not req.username or not req.password:
raise HTTPException(status_code=400, detail="Username or password is not provided.")
@@ -72,6 +72,7 @@ async def login(req: UserLoginData, session: AsyncSession = Depends(get_db)):
raise HTTPException(status_code=401, detail="Invalid password")
data = await authorize_user(sessions_repo, user, req.provider)
await uow.commit()
return data
if req.provider == "telegram":
@@ -82,8 +83,8 @@ async def login(req: UserLoginData, session: AsyncSession = Depends(get_db)):
@router.post("/refresh", response_model=UserTokens)
async def refresh(refresh_token: str, iss: ProvidersType, session: AsyncSession = Depends(get_db)):
sessions_repo = SessionsRepository(session)
async def refresh(refresh_token: str, iss: ProvidersType, uow: UnitOfWork = Depends(get_uow)):
sessions_repo = SessionsRepository(uow)
token_hash = hash_refresh_token(refresh_token)
token_entry = await sessions_repo.get_session_by_hash(token_hash)
@@ -92,6 +93,7 @@ async def refresh(refresh_token: str, iss: ProvidersType, session: AsyncSession
raise HTTPException(status_code=401, detail="Refresh token is invalid.")
key_pair = await refresh_token_rotation(sessions_repo, token_entry, iss)
await uow.commit()
return UserTokens(
access_token=key_pair.access_token,
refresh_token=key_pair.refresh_token,

View File

@@ -1,8 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from core.deps import get_service_identity
from db.session import get_db
from db.session import UnitOfWork, get_uow
from repositories.service_notifications import (
ack_notification,
get_pending_notifications,
@@ -22,9 +21,9 @@ router = APIRouter(prefix="/renewal")
async def get_pending(
limit: int = 50,
ctx: ServiceIdentity = Depends(get_service_identity),
session: AsyncSession = Depends(get_db),
uow: UnitOfWork = Depends(get_uow),
):
notifications = await get_pending_notifications(session, limit)
notifications = await get_pending_notifications(uow, limit)
response_users: list[UserNotificationData] = []
for notification in notifications:
@@ -38,8 +37,9 @@ async def get_pending(
)
)
await mark_notification_as_dispatched(session, notification.id)
await mark_notification_as_dispatched(uow, notification.id)
await uow.commit()
return NotificationResponse(users=response_users, issued_by=ctx.service)
@@ -47,13 +47,14 @@ async def get_pending(
async def acknowledge(
req: NotificationAcknowledgeRequest,
ctx: ServiceIdentity = Depends(get_service_identity),
session: AsyncSession = Depends(get_db),
uow: UnitOfWork = Depends(get_uow),
):
try:
r = await ack_notification(session, req.notification_id)
r = await ack_notification(uow, req.notification_id)
except Exception as e:
raise HTTPException(500, detail=str(e)) from None
if r:
await uow.commit()
return "OK"
raise HTTPException(500, detail="No such notification found.")

View File

@@ -2,11 +2,10 @@ import secrets
from datetime import UTC, datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from config import cfg
from core.deps import get_auth_context, get_service_identity
from db.session import get_db
from db.session import UnitOfWork, get_uow
from repositories.link_codes import create_link_code, get_link_code_by_code, use_link_code
from repositories.users import UserRepository
from schemas.dto import AuthContext
@@ -19,13 +18,14 @@ router = APIRouter(prefix="/link-codes")
@router.post("", response_model=LinkCodeResponse, status_code=201)
async def gen_link_code(
ctx: AuthContext = Depends(get_auth_context), session: AsyncSession = Depends(get_db)
ctx: AuthContext = Depends(get_auth_context), uow: UnitOfWork = Depends(get_uow)
):
code = secrets.token_urlsafe(cfg.link_code_length)
exp = datetime.now(UTC) + timedelta(minutes=cfg.link_code_ttl)
link_code = await create_link_code(
session, code=code, user_id=ctx.user.id, status=LinkCodeStatus.ACTIVE, expires_at=exp
uow, code=code, user_id=ctx.user.id, status=LinkCodeStatus.ACTIVE, expires_at=exp
)
await uow.commit()
return LinkCodeResponse(code=link_code.code, expires_at=link_code.expires_at)
@@ -34,9 +34,9 @@ async def gen_link_code(
async def consume_link_code(
payload: LinkCodeConsume,
ctx: AuthContext = Depends(get_service_identity),
session: AsyncSession = Depends(get_db),
uow: UnitOfWork = Depends(get_uow),
):
link_code = await get_link_code_by_code(session, payload.code)
link_code = await get_link_code_by_code(uow, payload.code)
if not link_code:
raise HTTPException(404, detail="Code not found")
@@ -44,7 +44,7 @@ async def consume_link_code(
if link_code.status != LinkCodeStatus.ACTIVE:
raise HTTPException(404, detail="Code expired or is invalid.")
users_repo = UserRepository(session)
users_repo = UserRepository(uow)
user = await users_repo.get_user_by_id(link_code.user_id)
if not user:
@@ -52,7 +52,8 @@ async def consume_link_code(
user = await users_repo.update_telegram_id(user, payload.telegram_id)
await use_link_code(session, code=link_code)
await use_link_code(uow, code=link_code)
await uow.commit()
return UserInfo(
username=user.username,

View File

@@ -2,12 +2,11 @@ import math
from datetime import UTC, datetime
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from config import cfg
from core.deps import get_auth_context, get_pally_client
from db.models.orders import OrderStatus
from db.session import get_db
from db.session import UnitOfWork, get_uow
from external.pally import PallyClient
from repositories import AddonsRepository, PricingRepository
from repositories.invoices import InvoiceRepository
@@ -31,13 +30,13 @@ router = APIRouter(prefix="/orders")
async def checkout(
order: OrderDetails,
ctx: AuthContext = Depends(get_auth_context),
session: AsyncSession = Depends(get_db),
uow: UnitOfWork = Depends(get_uow),
pally: PallyClient = Depends(get_pally_client),
):
addons_repo = AddonsRepository(session)
pricing_repo = PricingRepository(session)
invoices_repo = InvoiceRepository(session)
orders_repo = OrderRepository(session)
addons_repo = AddonsRepository(uow)
pricing_repo = PricingRepository(uow)
invoices_repo = InvoiceRepository(uow)
orders_repo = OrderRepository(uow)
pricing = await get_pricing_model(addons_repo, pricing_repo)
price = math.ceil(await calculate_price(addons_repo=addons_repo, order=order, pricing=pricing))
@@ -72,7 +71,7 @@ async def checkout(
raise HTTPException(500, detail="failed to create invoice")
else:
await deduct_order_balance(
session,
uow.session,
user=ctx.user,
order=order_entry,
description=f"order {order_entry.id} paid from balance",
@@ -85,7 +84,7 @@ async def checkout(
now=now,
):
await apply_order_now(
session,
uow.session,
user=ctx.user,
order=order_entry,
pricing=pricing,
@@ -95,9 +94,12 @@ async def checkout(
await queue_order_for_later(
order=order_entry, subscription=ctx.user.subscription, now=now
)
await session.commit()
await uow.commit()
payment_link = None
if amount_to_pay > 0:
await uow.commit()
return CheckoutResponse(
order_id=str(order_entry.id),
total_amount=price,

View File

@@ -3,10 +3,9 @@ import logging
from fastapi import Depends, Form, HTTPException
from fastapi.routing import APIRouter
from sqlalchemy.ext.asyncio import AsyncSession
from core.deps import get_db
from db.models.transactions import BalanceTransaction, BalanceTxType
from db.models.transactions import BalanceTxType
from db.session import UnitOfWork, get_uow
from external.pally import BillStatus
from repositories.invoices import InvoiceRepository
from repositories.users import UserRepository
@@ -39,7 +38,7 @@ async def pally_callback(
PayerComment: str | None = Form(None),
ErrorCode: int | None = Form(None),
ErrorMessage: str | None = Form(None),
session: AsyncSession = Depends(get_db),
uow: UnitOfWork = Depends(get_uow),
):
logger.info(
"Pally webhook received - InvId: %s, OutSum: %s, Commission: %s, TrsId: %s, Status: %s, "
@@ -88,7 +87,7 @@ async def pally_callback(
try:
await process_subscription_purchase(
session,
uow,
invoice_id=int(invoice_id_str),
trs_id=TrsId,
amount=amount,
@@ -102,9 +101,9 @@ async def pally_callback(
invoice_id_str,
TrsId,
)
await session.rollback()
await uow.rollback()
invoice_repo = InvoiceRepository(session)
invoice_repo = InvoiceRepository(uow)
invoice = await invoice_repo.get_by_id(int(invoice_id_str))
if invoice is None:
logger.critical(
@@ -117,7 +116,8 @@ async def pally_callback(
if invoice.status == InvoiceStatus.PAID:
return "OK"
user = await UserRepository(session).get_user_by_id(invoice.creator_id)
users_repo = UserRepository(uow)
user = await users_repo.get_user_by_id(invoice.creator_id)
if user is None:
logger.critical(
"Cannot credit fallback balance: user %s was not found " "for bill %s (TrsId: %s)",
@@ -127,22 +127,14 @@ async def pally_callback(
)
raise
balance_before = user.balance
user.balance += amount
invoice.status = InvoiceStatus.PAID
session.add(
BalanceTransaction(
user_id=user.id,
amount=amount,
tx_type=BalanceTxType.DEPOSIT,
balance_before=balance_before,
balance_after=user.balance,
description=(
f"fallback payment credit for invoice {invoice.id} " f"(TrsId: {TrsId})"
),
)
await users_repo.increase_balance(
user.id,
amount,
BalanceTxType.DEPOSIT,
f"fallback payment credit for invoice {invoice.id} (TrsId: {TrsId})",
)
await session.commit()
await invoice_repo.update_status_by_id(invoice.id, InvoiceStatus.PAID)
await uow.commit()
logger.info(
"Fallback payment credit processed: user_id=%s, amount=%s, " "invoice_id=%s, TrsId=%s",
user.id,

View File

@@ -1,7 +1,6 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from db.session import get_db
from db.session import UnitOfWork, get_uow
from repositories.addons import AddonsRepository
from repositories.pricing import PricingRepository
from schemas.plans import PricingPlans
@@ -11,9 +10,9 @@ 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)
async def get_plans(uow: UnitOfWork = Depends(get_uow)):
addons_repo = AddonsRepository(uow)
pricing_repo = PricingRepository(uow)
res = await get_pricing_model(addons_repo, pricing_repo)
return res