first commit
This commit is contained in:
4
app/services/__init__.py
Normal file
4
app/services/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from app.services.referral_service import ReferralService
|
||||
from app.services.user_service import UserService
|
||||
|
||||
__all__ = ("ReferralService", "UserService")
|
||||
57
app/services/referral_service.py
Normal file
57
app/services/referral_service.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from typing import Protocol
|
||||
|
||||
from app.repositories.protocols import IUserRepository
|
||||
|
||||
|
||||
class IReferralPayloadParser(Protocol):
|
||||
"""Parse Telegram deep-link payload (e.g. `ref_ABC123`) into internal referrer key."""
|
||||
|
||||
def parse(self, raw: str | None) -> str | None: ...
|
||||
|
||||
|
||||
class DefaultReferralPayloadParser:
|
||||
"""Placeholder: expects `ref_<code>`; extend with signed tokens or DB lookup later."""
|
||||
|
||||
PREFIX = "ref_"
|
||||
|
||||
def parse(self, raw: str | None) -> str | None:
|
||||
if not raw:
|
||||
return None
|
||||
if raw.startswith(self.PREFIX):
|
||||
return raw[len(self.PREFIX) :].strip() or None
|
||||
return None
|
||||
|
||||
|
||||
class ReferralService:
|
||||
"""
|
||||
Resolves referral payloads to referrer user rows.
|
||||
Placeholder: code is treated as referrer's telegram_id string for demo wiring;
|
||||
replace with dedicated referral codes table or signed deep links in production.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
users: IUserRepository,
|
||||
parser: IReferralPayloadParser | None = None,
|
||||
) -> None:
|
||||
self._users = users
|
||||
self._parser = parser or DefaultReferralPayloadParser()
|
||||
|
||||
async def resolve_referrer_id(
|
||||
self,
|
||||
start_payload: str | None,
|
||||
*,
|
||||
invitee_telegram_id: int,
|
||||
) -> int | None:
|
||||
code = self._parser.parse(start_payload)
|
||||
if not code:
|
||||
return None
|
||||
# Placeholder: interpret code as referrer's telegram_id (digits only).
|
||||
if not code.isdigit():
|
||||
return None
|
||||
referrer = await self._users.get_by_telegram_id(int(code))
|
||||
if referrer is None:
|
||||
return None
|
||||
if referrer.telegram_id == invitee_telegram_id:
|
||||
return None
|
||||
return referrer.id
|
||||
46
app/services/user_service.py
Normal file
46
app/services/user_service.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from app.models.user import User
|
||||
from app.repositories.protocols import IUserRepository
|
||||
from app.services.referral_service import ReferralService
|
||||
|
||||
|
||||
class UserService:
|
||||
def __init__(
|
||||
self,
|
||||
users: IUserRepository,
|
||||
referrals: ReferralService,
|
||||
) -> None:
|
||||
self._users = users
|
||||
self._referrals = referrals
|
||||
|
||||
async def ensure_user(
|
||||
self,
|
||||
*,
|
||||
telegram_id: int,
|
||||
username: str | None,
|
||||
first_name: str | None,
|
||||
start_payload: str | None,
|
||||
) -> User:
|
||||
referrer_internal_id = await self._referrals.resolve_referrer_id(
|
||||
start_payload,
|
||||
invitee_telegram_id=telegram_id,
|
||||
)
|
||||
existing = await self._users.get_by_telegram_id(telegram_id)
|
||||
if existing is not None:
|
||||
await self._users.update_profile(
|
||||
existing,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
)
|
||||
if referrer_internal_id is not None:
|
||||
await self._users.set_referrer_if_unset(
|
||||
existing,
|
||||
referrer_internal_id,
|
||||
)
|
||||
return existing
|
||||
|
||||
return await self._users.create(
|
||||
telegram_id=telegram_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
referrer_id=referrer_internal_id,
|
||||
)
|
||||
Reference in New Issue
Block a user