feat: parser, refactoring

This commit is contained in:
Artem Tsyrulnikov
2025-12-07 22:17:24 +03:00
parent e90c6bfb75
commit 2af23f84fe
39 changed files with 503 additions and 822 deletions

View File

@@ -5,7 +5,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
analytics_router = APIRouter(prefix='/analytics', tags=['analytics'])
@@ -13,43 +13,43 @@ analytics_router = APIRouter(prefix='/analytics', tags=['analytics'])
@analytics_router.get('/placements')
async def get_placements_analytics(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
target_channel_id: uuid.UUID | None = None,
) -> dto.GetPlacementsAnalyticsOutput:
input = dto.GetPlacementsAnalyticsInput(
user_id=current_user.user_id,
target_channel_id=target_channel_id,
)
return await dependencies.get_usecase().get_placements_analytics(input)
return await deps.get_usecase().get_placements_analytics(input)
@analytics_router.get('/creatives')
async def get_creatives_analytics(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
target_channel_id: uuid.UUID | None = None,
) -> dto.GetCreativesAnalyticsOutput:
input = dto.GetCreativesAnalyticsInput(
user_id=current_user.user_id,
target_channel_id=target_channel_id,
)
return await dependencies.get_usecase().get_creatives_analytics(input)
return await deps.get_usecase().get_creatives_analytics(input)
@analytics_router.get('/external-channels')
async def get_external_channels_analytics(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
target_channel_id: uuid.UUID | None = None,
) -> dto.GetExternalChannelsAnalyticsOutput:
input = dto.GetExternalChannelsAnalyticsInput(
user_id=current_user.user_id,
target_channel_id=target_channel_id,
)
return await dependencies.get_usecase().get_external_channels_analytics(input)
return await deps.get_usecase().get_external_channels_analytics(input)
@analytics_router.get('/spending')
async def get_spending_analytics(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
target_channel_id: uuid.UUID | None = None,
date_from: datetime.datetime | None = None,
date_to: datetime.datetime | None = None,
@@ -62,4 +62,4 @@ async def get_spending_analytics(
date_to=date_to,
grouping=grouping,
)
return await dependencies.get_usecase().get_spending_analytics(input)
return await deps.get_usecase().get_spending_analytics(input)

View File

@@ -3,7 +3,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
auth_router = APIRouter(prefix='/auth', tags=['auth'])
@@ -11,7 +11,7 @@ auth_router = APIRouter(prefix='/auth', tags=['auth'])
@auth_router.get('/complete')
async def complete_auth(token: str) -> dto.ValidateLoginTokenOutput:
return await dependencies.get_usecase().validate_login_token(
return await deps.get_usecase().validate_login_token(
input=dto.ValidateLoginTokenInput(
token=token,
)
@@ -20,6 +20,6 @@ async def complete_auth(token: str) -> dto.ValidateLoginTokenOutput:
@auth_router.get('/me')
async def get_me(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.UserOutput:
return await dependencies.get_usecase().get_me(user_id=current_user.user_id)
return await deps.get_usecase().get_me(user_id=current_user.user_id)

View File

@@ -4,7 +4,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
creatives_router = APIRouter(prefix='/creatives', tags=['creatives'])
@@ -12,7 +12,7 @@ creatives_router = APIRouter(prefix='/creatives', tags=['creatives'])
@creatives_router.get('')
async def list_creatives(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
target_channel_id: uuid.UUID | None = None,
include_archived: bool = False,
) -> dto.GetCreativesOutput:
@@ -22,37 +22,37 @@ async def list_creatives(
include_archived=include_archived,
)
return await dependencies.get_usecase().get_creatives(input=input)
return await deps.get_usecase().get_creatives(input=input)
@creatives_router.get('/{creative_id}')
async def get_creative(
creative_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.CreativeOutput:
input = dto.GetCreativeInput(
creative_id=creative_id,
user_id=current_user.user_id,
)
return await dependencies.get_usecase().get_creative(input=input)
return await deps.get_usecase().get_creative(input=input)
@creatives_router.post('')
async def create_creative(
request: dto.CreateCreativeInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.CreativeOutput:
return await dependencies.get_usecase().create_creative(request, current_user.user_id)
return await deps.get_usecase().create_creative(request, current_user.user_id)
@creatives_router.patch('/{creative_id}')
async def update_creative(
creative_id: uuid.UUID,
request: dto.UpdateCreativeInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.CreativeOutput:
return await dependencies.get_usecase().update_creative(
return await deps.get_usecase().update_creative(
creative_id=creative_id,
input=request,
user_id=current_user.user_id,
@@ -62,11 +62,11 @@ async def update_creative(
@creatives_router.delete('/{creative_id}')
async def delete_creative(
creative_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> None:
input = dto.DeleteCreativeInput(
creative_id=creative_id,
user_id=current_user.user_id,
)
await dependencies.get_usecase().delete_creative(input)
await deps.get_usecase().delete_creative(input)

View File

@@ -4,7 +4,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
external_channels_router = APIRouter(prefix='/external_channels', tags=['external_channels'])
@@ -13,7 +13,7 @@ external_channels_router = APIRouter(prefix='/external_channels', tags=['externa
@external_channels_router.get('/target/{target_channel_id}')
async def get_external_channels(
target_channel_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.GetExternalChannelsOutput:
"""Get all external channels for a specific target channel."""
input = dto.GetExternalChannelsInput(
@@ -21,16 +21,16 @@ async def get_external_channels(
user_id=current_user.user_id,
)
return await dependencies.get_usecase().get_external_channels(input=input)
return await deps.get_usecase().get_external_channels(input=input)
@external_channels_router.post('')
async def create_external_channel(
request: dto.CreateExternalChannelInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.ExternalChannelOutput:
"""Create a new external channel and link it to target channels."""
return await dependencies.get_usecase().create_external_channel(
return await deps.get_usecase().create_external_channel(
input=request,
user_id=current_user.user_id,
)
@@ -40,10 +40,10 @@ async def create_external_channel(
async def update_external_channel(
channel_id: uuid.UUID,
request: dto.UpdateExternalChannelInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.ExternalChannelOutput:
"""Update external channel fields (title, username, description, subscribers_count)."""
return await dependencies.get_usecase().update_external_channel(
return await deps.get_usecase().update_external_channel(
channel_id=channel_id,
input=request,
user_id=current_user.user_id,
@@ -54,14 +54,14 @@ async def update_external_channel(
async def update_external_channel_links(
channel_id: uuid.UUID,
request: dto.UpdateExternalChannelLinksInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.ExternalChannelOutput:
"""
Update target channel links for an existing external channel.
You can add new target channels, remove existing ones, or both in a single request.
"""
return await dependencies.get_usecase().update_external_channel_links(
return await deps.get_usecase().update_external_channel_links(
channel_id=channel_id,
input=request,
user_id=current_user.user_id,
@@ -71,7 +71,7 @@ async def update_external_channel_links(
@external_channels_router.delete('/{channel_id}')
async def delete_external_channel(
channel_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> None:
"""Delete an external channel."""
input = dto.DeleteExternalChannelInput(
@@ -79,4 +79,4 @@ async def delete_external_channel(
user_id=current_user.user_id,
)
await dependencies.get_usecase().delete_external_channel(input=input)
await deps.get_usecase().delete_external_channel(input=input)

View File

@@ -4,7 +4,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
placements_router = APIRouter(prefix='/placements', tags=['placements'])
@@ -12,7 +12,7 @@ placements_router = APIRouter(prefix='/placements', tags=['placements'])
@placements_router.get('')
async def list_placements(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
target_channel_id: uuid.UUID | None = None,
external_channel_id: uuid.UUID | None = None,
creative_id: uuid.UUID | None = None,
@@ -26,37 +26,37 @@ async def list_placements(
include_archived=include_archived,
)
return await dependencies.get_usecase().get_placements(input=input)
return await deps.get_usecase().get_placements(input=input)
@placements_router.get('/{placement_id}')
async def get_placement(
placement_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.PlacementOutput:
input = dto.GetPlacementInput(
placement_id=placement_id,
user_id=current_user.user_id,
)
return await dependencies.get_usecase().get_placement(input=input)
return await deps.get_usecase().get_placement(input=input)
@placements_router.post('')
async def create_placement(
request: dto.CreatePlacementInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.PlacementOutput:
return await dependencies.get_usecase().create_placement(request, current_user.user_id)
return await deps.get_usecase().create_placement(request, current_user.user_id)
@placements_router.patch('/{placement_id}')
async def update_placement(
placement_id: uuid.UUID,
request: dto.UpdatePlacementInput,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.PlacementOutput:
return await dependencies.get_usecase().update_placement(
return await deps.get_usecase().update_placement(
placement_id=placement_id,
input=request,
user_id=current_user.user_id,
@@ -66,11 +66,11 @@ async def update_placement(
@placements_router.delete('/{placement_id}')
async def delete_placement(
placement_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> None:
input = dto.DeletePlacementInput(
placement_id=placement_id,
user_id=current_user.user_id,
)
await dependencies.get_usecase().delete_placement(input)
await deps.get_usecase().delete_placement(input)

View File

@@ -4,7 +4,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
target_channels_router = APIRouter(prefix='/target_channels', tags=['target_channels'])
@@ -12,23 +12,23 @@ target_channels_router = APIRouter(prefix='/target_channels', tags=['target_chan
@target_channels_router.get('')
async def get_user_target_chans(
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.GetUserTargetChansOutput:
input = dto.GetUserTargetChansInput(
user_id=current_user.user_id,
)
return await dependencies.get_usecase().get_user_target_chans(input=input)
return await deps.get_usecase().get_user_target_chans(input=input)
@target_channels_router.delete('/{channel_id}')
async def disconnect_target_chan(
channel_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> None:
input = dto.DisconnectTargetChanInput(
channel_id=channel_id,
user_id=current_user.user_id,
)
await dependencies.get_usecase().disconnect_target_chan(input=input)
await deps.get_usecase().disconnect_target_chan(input=input)

View File

@@ -5,7 +5,7 @@ from typing import Annotated
from fastapi import Depends
from fastapi.routing import APIRouter
from src import dependencies, dto
from src import deps, dto
from src.adapter.jwt import JWTPayload
views_router = APIRouter(prefix='/placements/{placement_id}/views', tags=['views'])
@@ -14,7 +14,7 @@ views_router = APIRouter(prefix='/placements/{placement_id}/views', tags=['views
@views_router.get('/history')
async def get_views_history(
placement_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
from_date: datetime.datetime | None = None,
to_date: datetime.datetime | None = None,
) -> dto.GetViewsHistoryOutput:
@@ -25,27 +25,14 @@ async def get_views_history(
to_date=to_date,
)
return await dependencies.get_usecase().get_views_history(input=input_data)
@views_router.post('/fetch')
async def fetch_views(
placement_id: uuid.UUID,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
) -> dto.FetchViewsManuallyOutput:
input_data = dto.FetchViewsInputManually(
placement_id=placement_id,
user_id=current_user.user_id,
)
return await dependencies.get_usecase().fetch_views(input=input_data)
return await deps.get_usecase().get_views_history(input=input_data)
@views_router.post('/manual')
async def update_views_manually(
placement_id: uuid.UUID,
views_count: int,
current_user: Annotated[JWTPayload, Depends(dependencies.get_current_user)],
current_user: Annotated[JWTPayload, Depends(deps.get_current_user)],
) -> dto.PlacementOutput:
input_data = dto.UpdateViewsManuallyInput(
placement_id=placement_id,
@@ -53,4 +40,4 @@ async def update_views_manually(
views_count=views_count,
)
return await dependencies.get_usecase().update_views_manually(input=input_data)
return await deps.get_usecase().update_views_manually(input=input_data)

View File

@@ -2,7 +2,7 @@ import logging
from aiogram.types import ChatJoinRequest
from src import dependencies
from src import deps
from src.controller.telegram_callback import telegram_callback_router
log = logging.getLogger(__name__)
@@ -23,7 +23,7 @@ async def on_chat_join_request(event: ChatJoinRequest) -> None:
log.debug('No invite_link in chat join request event')
return
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
await usecase.handle_subscription(
user_telegram_id=event.from_user.id,

View File

@@ -2,7 +2,7 @@ import logging
from aiogram.types import ChatMemberUpdated
from src import dependencies
from src import deps
from src.controller.telegram_callback import telegram_callback_router
log = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ async def on_chat_member_updated(event: ChatMemberUpdated) -> None:
is_now_not_member = new_status in ('left', 'kicked')
user_left = was_member and is_now_not_member
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
if user_joined:
invite_link = event.invite_link.invite_link if event.invite_link else None

View File

@@ -4,7 +4,7 @@ from aiogram import F
from aiogram.filters import Command
from aiogram.types import CallbackQuery, Message
from src import dependencies
from src import deps
from src.controller.telegram_callback import telegram_callback_router
log = logging.getLogger(__name__)
@@ -16,7 +16,7 @@ async def cmd_new_creative(message: Message) -> None:
log.error('Failed to get user data from message')
return
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
await usecase.start_creative_creation(telegram_id=message.from_user.id, chat_id=message.chat.id)
@@ -26,7 +26,7 @@ async def callback_creative_channel(callback: CallbackQuery) -> None:
log.error('Failed to get required data from callback query')
return
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
await usecase.handle_creative_callback(
telegram_id=callback.from_user.id,
chat_id=callback.message.chat.id,
@@ -42,7 +42,7 @@ async def handle_text_message(message: Message) -> None:
if not message.from_user or not message.text:
return
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
await usecase.handle_creative_text_input(
telegram_id=message.from_user.id,
chat_id=message.chat.id,

View File

@@ -3,7 +3,7 @@ import logging
from aiogram.types import ChatMemberAdministrator, ChatMemberUpdated
from fastapi import HTTPException
from src import dependencies, dto
from src import deps, dto
from src.controller.telegram_callback import telegram_callback_router
log = logging.getLogger(__name__)
@@ -18,7 +18,7 @@ async def on_bot_added_to_chat(event: ChatMemberUpdated) -> None:
log.error('Failed to get user data from chat member update')
return
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
old_status = event.old_chat_member.status
new_status = event.new_chat_member.status

View File

@@ -3,7 +3,7 @@ import logging
from aiogram.filters import CommandStart
from aiogram.types import Message
from src import dependencies, dto
from src import deps, dto
from src.controller.telegram_callback import telegram_callback_router
log = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ async def cmd_start_with_deeplink(message: Message) -> None:
chat_id=message.chat.id,
)
await dependencies.get_usecase().telegram_login(input=input)
await deps.get_usecase().telegram_login(input=input)
@telegram_callback_router.message(CommandStart())
@@ -37,5 +37,5 @@ async def cmd_start(message: Message) -> None:
log.error('Failed to get user data from message')
return
usecase = dependencies.get_usecase()
usecase = deps.get_usecase()
await usecase.telegram_start(telegram_id=message.from_user.id, chat_id=message.chat.id)

View File

@@ -1,15 +0,0 @@
import logging
from typing import TYPE_CHECKING
from shared.worker_base import WorkerBase
from src import dependencies
if TYPE_CHECKING:
pass
log = logging.getLogger(__name__)
class FetchViewsWorker(WorkerBase):
async def _cycle_func(self) -> None:
await dependencies.get_usecase().run_views_worker_cycle(self.config.INTERVAL_SECONDS)