Initial commit
This commit is contained in:
0
services/ingest-service/app/__init__.py
Normal file
0
services/ingest-service/app/__init__.py
Normal file
0
services/ingest-service/app/api/__init__.py
Normal file
0
services/ingest-service/app/api/__init__.py
Normal file
118
services/ingest-service/app/api/uis.py
Normal file
118
services/ingest-service/app/api/uis.py
Normal file
@@ -0,0 +1,118 @@
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app import crud
|
||||
|
||||
class CallDirection(str, Enum):
|
||||
in_ = "in"
|
||||
out = "out"
|
||||
|
||||
|
||||
class UisCallEvent(BaseModel):
|
||||
eventType: str
|
||||
call_session_id: str
|
||||
direction: CallDirection
|
||||
employee_id: int
|
||||
employee_full_name: str
|
||||
contact_phone_number: str
|
||||
called_phone_number: str
|
||||
communication_group_name: str
|
||||
start_time: datetime
|
||||
finish_time: datetime
|
||||
talk_time_duration: int
|
||||
full_record_file_link: HttpUrl
|
||||
campaign_name: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class CallEventResponse(BaseModel):
|
||||
id: int
|
||||
event_type: str
|
||||
call_session_id: str
|
||||
direction: str
|
||||
employee_id: int
|
||||
employee_full_name: str
|
||||
contact_phone_number: str
|
||||
called_phone_number: str
|
||||
communication_group_name: str
|
||||
start_time: datetime
|
||||
finish_time: datetime
|
||||
talk_time_duration: int
|
||||
full_record_file_link: str
|
||||
campaign_name: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/webhook", response_model=CallEventResponse, status_code=201)
|
||||
async def create_call_event(callEvent: UisCallEvent, db: AsyncSession = Depends(get_db)):
|
||||
"""Webhook для получения событий звонков от UIS"""
|
||||
# Проверяем, не существует ли уже событие с таким call_session_id
|
||||
existing_event = await crud.get_call_event_by_session_id(db, callEvent.call_session_id)
|
||||
if existing_event:
|
||||
raise HTTPException(status_code=400, detail="Call event with this session_id already exists")
|
||||
|
||||
# Преобразуем Pydantic модель в словарь для БД
|
||||
call_event_data = {
|
||||
"event_type": callEvent.eventType,
|
||||
"call_session_id": callEvent.call_session_id,
|
||||
"direction": callEvent.direction,
|
||||
"employee_id": callEvent.employee_id,
|
||||
"employee_full_name": callEvent.employee_full_name,
|
||||
"contact_phone_number": callEvent.contact_phone_number,
|
||||
"called_phone_number": callEvent.called_phone_number,
|
||||
"communication_group_name": callEvent.communication_group_name,
|
||||
"start_time": callEvent.start_time,
|
||||
"finish_time": callEvent.finish_time,
|
||||
"talk_time_duration": callEvent.talk_time_duration,
|
||||
"full_record_file_link": str(callEvent.full_record_file_link),
|
||||
"campaign_name": callEvent.campaign_name,
|
||||
}
|
||||
|
||||
db_call_event = await crud.create_call_event(db, call_event_data)
|
||||
return db_call_event
|
||||
|
||||
|
||||
@router.get("/events", response_model=List[CallEventResponse])
|
||||
async def get_call_events(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить список всех событий звонков"""
|
||||
events = await crud.get_all_call_events(db, skip=skip, limit=limit)
|
||||
return events
|
||||
|
||||
|
||||
@router.get("/events/{call_session_id}", response_model=CallEventResponse)
|
||||
async def get_call_event(call_session_id: str, db: AsyncSession = Depends(get_db)):
|
||||
"""Получить событие звонка по session_id"""
|
||||
event = await crud.get_call_event_by_session_id(db, call_session_id)
|
||||
if not event:
|
||||
raise HTTPException(status_code=404, detail="Call event not found")
|
||||
return event
|
||||
|
||||
|
||||
@router.get("/events/employee/{employee_id}", response_model=List[CallEventResponse])
|
||||
async def get_employee_call_events(
|
||||
employee_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить все звонки конкретного сотрудника"""
|
||||
events = await crud.get_call_events_by_employee(db, employee_id, skip=skip, limit=limit)
|
||||
return events
|
||||
22
services/ingest-service/app/infrastructure/database.py
Normal file
22
services/ingest-service/app/infrastructure/database.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
# Для Alembic используем синхронный движок с psycopg2
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:5432/ingest_db")
|
||||
# Преобразуем asyncpg URL в psycopg2 для синхронного движка
|
||||
SYNC_DATABASE_URL = DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://")
|
||||
|
||||
engine = create_engine(SYNC_DATABASE_URL)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
11
services/ingest-service/app/main.py
Normal file
11
services/ingest-service/app/main.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from app.api.uis import router as uis_router
|
||||
|
||||
app = FastAPI(
|
||||
title="Ingest Service API",
|
||||
description="Микросервис для приема событий звонков",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
app.include_router(uis_router, prefix="/v1/uis", tags=["UIS Webhooks"])
|
||||
35
services/ingest-service/app/models.py
Normal file
35
services/ingest-service/app/models.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Enum as SQLEnum
|
||||
from app.infrastructure import Base
|
||||
import enum
|
||||
|
||||
|
||||
class CallDirectionEnum(str, enum.Enum):
|
||||
in_ = "in"
|
||||
out = "out"
|
||||
|
||||
|
||||
class CallEvent(Base):
|
||||
"""Модель для хранения событий звонков"""
|
||||
__tablename__ = "call_events"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
event_type = Column(String, nullable=False)
|
||||
call_session_id = Column(String, unique=True, index=True, nullable=False)
|
||||
direction = Column(SQLEnum(CallDirectionEnum), nullable=False)
|
||||
employee_id = Column(Integer, nullable=False, index=True)
|
||||
employee_full_name = Column(String, nullable=False)
|
||||
contact_phone_number = Column(String, nullable=False)
|
||||
called_phone_number = Column(String, nullable=False)
|
||||
communication_group_name = Column(String)
|
||||
start_time = Column(DateTime, nullable=False)
|
||||
finish_time = Column(DateTime, nullable=False)
|
||||
talk_time_duration = Column(Integer, nullable=False)
|
||||
full_record_file_link = Column(String, nullable=False)
|
||||
campaign_name = Column(String)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CallEvent(id={self.id}, call_session_id={self.call_session_id})>"
|
||||
|
||||
4
services/ingest-service/app/models/__init__.py
Normal file
4
services/ingest-service/app/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from app.models.call_event import CallEvent
|
||||
|
||||
__all__ = ["CallEvent"]
|
||||
|
||||
20
services/ingest-service/app/models/call_event.py
Normal file
20
services/ingest-service/app/models/call_event.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from sqlalchemy import Column, BigInteger, Integer, UUID, String
|
||||
from app.core.database import Base
|
||||
|
||||
class CallEvent(Base):
|
||||
__tablename__ = "call_events"
|
||||
|
||||
id = Column(UUID, primary_key=True, index=True)
|
||||
call_session_id = Column(BigInteger, nullable=False, index=True)
|
||||
direction = Column(String, nullable=False)
|
||||
notification_mnemonic = Column(String, nullable=False)
|
||||
last_answered_employee_full_name = Column(String, nullable=True)
|
||||
employee_id = Column(Integer, nullable=True)
|
||||
finish_time = Column(Integer, nullable=False)
|
||||
total_time_duration = Column(Integer, nullable=False)
|
||||
wait_time_duration = Column(Integer, nullable=False)
|
||||
total_wait_time_duration = Column(Integer, nullable=False)
|
||||
talk_time_duration = Column(Integer, nullable=False)
|
||||
clean_talk_time_duration = Column(Integer, nullable=False)
|
||||
full_record_file_link = Column(String, nullable=False)
|
||||
tcm_topcrm_notification_name = Column(String, nullable=False)
|
||||
Reference in New Issue
Block a user