feat: creatives and external channels
This commit is contained in:
210
src/adapter/postgres.py
Normal file
210
src/adapter/postgres.py
Normal file
@@ -0,0 +1,210 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from shared.datebase_base import DatabaseBase
|
||||
from src import domain
|
||||
|
||||
|
||||
class Postgres(DatabaseBase):
|
||||
async def get_user(self, *, user_id: uuid.UUID | None = None, telegram_id: int | None = None) -> domain.User | None:
|
||||
if user_id:
|
||||
return await self.session.get(domain.User, user_id)
|
||||
|
||||
elif telegram_id:
|
||||
stmt = select(domain.User).where(domain.User.telegram_id == telegram_id)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
raise ValueError('Either user_id or telegram_id must be provided')
|
||||
|
||||
async def create_user(self, user: domain.User) -> domain.User:
|
||||
self.session.add(user)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(user)
|
||||
return user
|
||||
|
||||
async def create_login_token(
|
||||
self, user_id: uuid.UUID, token: str, expires_at: datetime.datetime
|
||||
) -> domain.LoginToken:
|
||||
login_token = domain.LoginToken(user_id=user_id, token=token, expires_at=expires_at)
|
||||
|
||||
self.session.add(login_token)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(login_token)
|
||||
return login_token
|
||||
|
||||
async def get_login_token(self, token: str) -> domain.LoginToken | None:
|
||||
stmt = select(domain.LoginToken).where(domain.LoginToken.token == token)
|
||||
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def mark_token_as_used(self, token: str) -> None:
|
||||
stmt = select(domain.LoginToken).where(domain.LoginToken.token == token)
|
||||
|
||||
result = await self.session.execute(stmt)
|
||||
login_token = result.scalar_one_or_none()
|
||||
if login_token:
|
||||
login_token.used_at = datetime.datetime.now(datetime.UTC)
|
||||
await self.session.flush()
|
||||
|
||||
async def get_target_channel(
|
||||
self, user_id: uuid.UUID, channel_id: uuid.UUID | None = None, telegram_id: int | None = None
|
||||
) -> domain.TargetChannel | None:
|
||||
if not channel_id and not telegram_id:
|
||||
raise ValueError('Either channel_id or telegram_id must be provided')
|
||||
|
||||
stmt = select(domain.TargetChannel).where(domain.TargetChannel.user_id == user_id)
|
||||
if channel_id:
|
||||
stmt = stmt.where(domain.TargetChannel.id == channel_id)
|
||||
if telegram_id:
|
||||
stmt = stmt.where(domain.TargetChannel.telegram_id == telegram_id)
|
||||
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create_target_channel(self, channel: domain.TargetChannel) -> domain.TargetChannel:
|
||||
self.session.add(channel)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(channel)
|
||||
return channel
|
||||
|
||||
async def upsert_target_channel(self, channel: domain.TargetChannel) -> domain.TargetChannel:
|
||||
existing = await self.get_target_channel(user_id=channel.user_id, telegram_id=channel.telegram_id)
|
||||
|
||||
if not existing:
|
||||
self.session.add(channel)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(channel)
|
||||
return channel
|
||||
|
||||
existing.title = channel.title
|
||||
existing.username = channel.username
|
||||
existing.user_id = channel.user_id
|
||||
existing.is_active = channel.is_active
|
||||
existing.deleted_at = None
|
||||
existing.updated_at = datetime.datetime.now(datetime.UTC)
|
||||
|
||||
await self.session.flush()
|
||||
await self.session.refresh(existing)
|
||||
return existing
|
||||
|
||||
async def get_user_target_channels(self, user_id: uuid.UUID) -> list[domain.TargetChannel]:
|
||||
stmt = select(domain.TargetChannel).where(
|
||||
domain.TargetChannel.status == domain.TargetChannelStatus.ACTIVE,
|
||||
domain.TargetChannel.user_id == user_id,
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def update_target_channel(self, channel: domain.TargetChannel) -> domain.TargetChannel:
|
||||
await self.session.flush()
|
||||
await self.session.refresh(channel)
|
||||
return channel
|
||||
|
||||
async def get_external_channel(
|
||||
self, user_id: uuid.UUID, channel_id: uuid.UUID | None = None, telegram_id: int | None = None
|
||||
) -> domain.ExternalChannel | None:
|
||||
stmt = select(domain.ExternalChannel).where(domain.ExternalChannel.user_id == user_id)
|
||||
if channel_id:
|
||||
stmt = stmt.where(domain.ExternalChannel.id == channel_id)
|
||||
if telegram_id:
|
||||
stmt = stmt.where(domain.ExternalChannel.telegram_id == telegram_id)
|
||||
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create_external_channel(self, channel: domain.ExternalChannel) -> domain.ExternalChannel:
|
||||
self.session.add(channel)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(channel)
|
||||
return channel
|
||||
|
||||
async def get_external_channels_for_target(self, target_channel_id: uuid.UUID) -> list[domain.ExternalChannel]:
|
||||
stmt = (
|
||||
select(domain.ExternalChannel)
|
||||
.join(domain.ExternalChannel.target_channels)
|
||||
.where(domain.TargetChannel.id == target_channel_id)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def add_external_channel_to_targets(
|
||||
self, external_channel_id: uuid.UUID, target_channel_ids: list[uuid.UUID]
|
||||
) -> None:
|
||||
external_channel = await self.session.get(domain.ExternalChannel, external_channel_id)
|
||||
if not external_channel:
|
||||
raise ValueError(f'ExternalChannel {external_channel_id} not found')
|
||||
|
||||
for target_id in target_channel_ids:
|
||||
target_channel = await self.session.get(domain.TargetChannel, target_id)
|
||||
if target_channel and target_channel not in external_channel.target_channels:
|
||||
external_channel.target_channels.append(target_channel)
|
||||
|
||||
await self.session.flush()
|
||||
|
||||
async def remove_external_channel_from_target(
|
||||
self, external_channel_id: uuid.UUID, target_channel_id: uuid.UUID
|
||||
) -> None:
|
||||
external_channel = await self.session.get(domain.ExternalChannel, external_channel_id)
|
||||
if not external_channel:
|
||||
raise ValueError(f'ExternalChannel {external_channel_id} not found')
|
||||
|
||||
target_channel = await self.session.get(domain.TargetChannel, target_channel_id)
|
||||
if target_channel and target_channel in external_channel.target_channels:
|
||||
external_channel.target_channels.remove(target_channel)
|
||||
|
||||
await self.session.flush()
|
||||
|
||||
async def delete_external_channel(self, channel_id: uuid.UUID) -> None:
|
||||
stmt = delete(domain.ExternalChannel).where(domain.ExternalChannel.id == channel_id)
|
||||
await self.session.execute(stmt)
|
||||
await self.session.flush()
|
||||
|
||||
async def get_creative(self, creative_id: uuid.UUID, user_id: uuid.UUID) -> domain.Creative | None:
|
||||
stmt = (
|
||||
select(domain.Creative)
|
||||
.options(selectinload(domain.Creative.target_channel))
|
||||
.where(domain.Creative.id == creative_id, domain.Creative.user_id == user_id)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create_creative(self, creative: domain.Creative) -> domain.Creative:
|
||||
self.session.add(creative)
|
||||
await self.session.flush()
|
||||
await self.session.refresh(creative)
|
||||
return creative
|
||||
|
||||
async def update_creative(self, creative: domain.Creative) -> domain.Creative:
|
||||
await self.session.flush()
|
||||
await self.session.refresh(creative)
|
||||
return creative
|
||||
|
||||
async def get_user_creatives(
|
||||
self, user_id: uuid.UUID, *, target_channel_id: uuid.UUID | None = None, include_archived: bool = False
|
||||
) -> list[domain.Creative]:
|
||||
stmt = (
|
||||
select(domain.Creative)
|
||||
.options(selectinload(domain.Creative.target_channel))
|
||||
.where(domain.Creative.user_id == user_id)
|
||||
)
|
||||
|
||||
if target_channel_id:
|
||||
stmt = stmt.where(domain.Creative.target_channel_id == target_channel_id)
|
||||
|
||||
if not include_archived:
|
||||
stmt = stmt.where(domain.Creative.status == domain.CreativeStatus.ACTIVE)
|
||||
|
||||
stmt = stmt.order_by(domain.Creative.created_at.desc())
|
||||
|
||||
result = await self.session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def delete_creative(self, creative_id: uuid.UUID) -> None:
|
||||
stmt = delete(domain.Creative).where(domain.Creative.id == creative_id)
|
||||
await self.session.execute(stmt)
|
||||
await self.session.flush()
|
||||
Reference in New Issue
Block a user