Brushed up the backend, added writing task 1 academic prompt gen and grading ENCOA-274

This commit is contained in:
Carlos-Mesquita
2024-12-10 22:24:40 +00:00
parent 68cab80851
commit 6982068864
167 changed files with 1411 additions and 1229 deletions

View File

@@ -38,4 +38,4 @@ EXPOSE 8000
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
ENTRYPOINT ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "1", "--threads", "8", "--timeout", "0", "-k", "uvicorn.workers.UvicornWorker", "app.server:app"]
ENTRYPOINT ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "1", "--threads", "8", "--timeout", "0", "-k", "uvicorn.workers.UvicornWorker", "ielts_be:app"]

2
app.py
View File

@@ -13,7 +13,7 @@ load_dotenv()
)
def main(env: str):
uvicorn.run(
app="app.server:app",
app="ielts_be:app",
host="localhost",
port=8000,
reload=True if env != "production" else False,

View File

@@ -1,28 +0,0 @@
from fastapi import APIRouter
from .listening import listening_router
from .reading import reading_router
from .speaking import speaking_router
from .training import training_router
from .writing import writing_router
from .grade import grade_router
from .user import user_router
from .level import level_router
router = APIRouter(prefix="/api", tags=["Home"])
@router.get('/healthcheck')
async def healthcheck():
return {"healthy": True}
exercises_router = APIRouter()
exercises_router.include_router(listening_router, prefix="/listening", tags=["Listening"])
exercises_router.include_router(reading_router, prefix="/reading", tags=["Reading"])
exercises_router.include_router(speaking_router, prefix="/speaking", tags=["Speaking"])
exercises_router.include_router(writing_router, prefix="/writing", tags=["Writing"])
exercises_router.include_router(level_router, prefix="/level", tags=["Level"])
router.include_router(grade_router, prefix="/grade", tags=["Grade"])
router.include_router(training_router, prefix="/training", tags=["Training"])
router.include_router(user_router, prefix="/user", tags=["Users"])
router.include_router(exercises_router)

View File

@@ -1,27 +0,0 @@
import random
from dependency_injector.wiring import inject, Provide
from fastapi import APIRouter, Path, Query, Depends
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.configs.constants import EducationalContent
from app.controllers.abc import IWritingController
controller = "writing_controller"
writing_router = APIRouter()
@writing_router.get(
'/{task}',
dependencies=[Depends(Authorized([IsAuthenticatedViaBearerToken]))]
)
@inject
async def generate_writing(
task: int = Path(..., ge=1, le=2),
difficulty: str = Query(default=None),
topic: str = Query(default=None),
writing_controller: IWritingController = Depends(Provide[controller])
):
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
topic = random.choice(EducationalContent.MTI_TOPICS) if not topic else topic
return await writing_controller.get_writing_task_general_question(task, topic, difficulty)

View File

@@ -1,10 +0,0 @@
from abc import ABC, abstractmethod
from app.dtos.user_batch import BatchUsersDTO
class IUserController(ABC):
@abstractmethod
async def batch_import(self, batch: BatchUsersDTO):
pass

View File

@@ -1,8 +0,0 @@
from abc import ABC, abstractmethod
class IWritingController(ABC):
@abstractmethod
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
pass

View File

@@ -1,11 +0,0 @@
from app.controllers.abc import IWritingController
from app.services.abc import IWritingService
class WritingController(IWritingController):
def __init__(self, writing_service: IWritingService):
self._service = writing_service
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
return await self._service.get_writing_task_general_question(task, topic, difficulty)

View File

View File

@@ -1,8 +0,0 @@
from .document_stores import *
from app.repositories.impl.file_storage.firebase import FirebaseStorage
__all__ = [
"FirebaseStorage"
]
__all__.extend(document_stores.__all__)

View File

@@ -1,156 +1,156 @@
import json
import os
import pathlib
import logging.config
import logging.handlers
import aioboto3
import contextlib
from contextlib import asynccontextmanager
from collections import defaultdict
from typing import List
from http import HTTPStatus
import httpx
from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.middleware import Middleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import nltk
from starlette import status
from app.api import router
from app.configs import DependencyInjector
from app.exceptions import CustomException
from app.middlewares import AuthenticationMiddleware, AuthBackend
from app.services.impl import OpenAIWhisper
@asynccontextmanager
async def lifespan(_app: FastAPI):
"""
Startup and Shutdown logic is in this lifespan method
https://fastapi.tiangolo.com/advanced/events/
"""
# NLTK required datasets download
nltk.download('words')
nltk.download("punkt")
# AWS Polly client instantiation
context_stack = contextlib.AsyncExitStack()
session = aioboto3.Session()
polly_client = await context_stack.enter_async_context(
session.client(
'polly',
region_name='eu-west-1',
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID")
)
)
http_client = httpx.AsyncClient()
stt = OpenAIWhisper()
DependencyInjector(
polly_client,
http_client,
stt
).inject()
# Setup logging
config_file = pathlib.Path("./app/configs/logging/logging_config.json")
with open(config_file) as f_in:
config = json.load(f_in)
logging.config.dictConfig(config)
yield
stt.close()
await http_client.aclose()
await polly_client.close()
await context_stack.aclose()
def setup_listeners(_app: FastAPI) -> None:
@_app.exception_handler(RequestValidationError)
async def custom_form_validation_error(request, exc):
"""
Don't delete request param
"""
reformatted_message = defaultdict(list)
for pydantic_error in exc.errors():
loc, msg = pydantic_error["loc"], pydantic_error["msg"]
filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc
field_string = ".".join(filtered_loc)
if field_string == "cookie.refresh_token":
return JSONResponse(
status_code=401,
content={"error_code": 401, "message": HTTPStatus.UNAUTHORIZED.description},
)
reformatted_message[field_string].append(msg)
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content=jsonable_encoder(
{"details": "Invalid request!", "errors": reformatted_message}
),
)
@_app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
"""
Don't delete request param
"""
return JSONResponse(
status_code=exc.code,
content={"error_code": exc.error_code, "message": exc.message},
)
@_app.exception_handler(Exception)
async def default_exception_handler(request: Request, exc: Exception):
"""
Don't delete request param
"""
return JSONResponse(
status_code=500,
content=str(exc),
)
def setup_middleware() -> List[Middleware]:
middleware = [
Middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
),
Middleware(
AuthenticationMiddleware,
backend=AuthBackend()
)
]
return middleware
def create_app() -> FastAPI:
env = os.getenv("ENV")
_app = FastAPI(
docs_url="/docs" if env != "production" else None,
redoc_url="/redoc" if env != "production" else None,
middleware=setup_middleware(),
lifespan=lifespan
)
_app.include_router(router)
setup_listeners(_app)
return _app
app = create_app()
import json
import os
import pathlib
import logging.config
import logging.handlers
import aioboto3
import contextlib
from contextlib import asynccontextmanager
from collections import defaultdict
from typing import List
from http import HTTPStatus
import httpx
from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.middleware import Middleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import nltk
from starlette import status
from ielts_be.api import router
from ielts_be.configs import DependencyInjector
from ielts_be.exceptions import CustomException
from ielts_be.middlewares import AuthenticationMiddleware, AuthBackend
from ielts_be.services.impl import OpenAIWhisper
@asynccontextmanager
async def lifespan(_app: FastAPI):
"""
Startup and Shutdown logic is in this lifespan method
https://fastapi.tiangolo.com/advanced/events/
"""
# NLTK required datasets download
nltk.download('words')
nltk.download("punkt")
# AWS Polly client instantiation
context_stack = contextlib.AsyncExitStack()
session = aioboto3.Session()
polly_client = await context_stack.enter_async_context(
session.client(
'polly',
region_name='eu-west-1',
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID")
)
)
http_client = httpx.AsyncClient()
stt = OpenAIWhisper()
DependencyInjector(
polly_client,
http_client,
stt
).inject()
# Setup logging
config_file = pathlib.Path("./ielts_be/configs/logging/logging_config.json")
with open(config_file) as f_in:
config = json.load(f_in)
logging.config.dictConfig(config)
yield
stt.close()
await http_client.aclose()
await polly_client.close()
await context_stack.aclose()
def setup_listeners(_app: FastAPI) -> None:
@_app.exception_handler(RequestValidationError)
async def custom_form_validation_error(request, exc):
"""
Don't delete request param
"""
reformatted_message = defaultdict(list)
for pydantic_error in exc.errors():
loc, msg = pydantic_error["loc"], pydantic_error["msg"]
filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc
field_string = ".".join(filtered_loc)
if field_string == "cookie.refresh_token":
return JSONResponse(
status_code=401,
content={"error_code": 401, "message": HTTPStatus.UNAUTHORIZED.description},
)
reformatted_message[field_string].append(msg)
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content=jsonable_encoder(
{"details": "Invalid request!", "errors": reformatted_message}
),
)
@_app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
"""
Don't delete request param
"""
return JSONResponse(
status_code=exc.code,
content={"error_code": exc.error_code, "message": exc.message},
)
@_app.exception_handler(Exception)
async def default_exception_handler(request: Request, exc: Exception):
"""
Don't delete request param
"""
return JSONResponse(
status_code=500,
content=str(exc),
)
def setup_middleware() -> List[Middleware]:
middleware = [
Middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
),
Middleware(
AuthenticationMiddleware,
backend=AuthBackend()
)
]
return middleware
def create_app() -> FastAPI:
env = os.getenv("ENV")
_app = FastAPI(
docs_url="/docs" if env != "production" else None,
redoc_url="/redoc" if env != "production" else None,
middleware=setup_middleware(),
lifespan=lifespan
)
_app.include_router(router)
setup_listeners(_app)
return _app
app = create_app()

15
ielts_be/api/__init__.py Normal file
View File

@@ -0,0 +1,15 @@
from fastapi import APIRouter
from .training import training_router
from .user import user_router
from .exam import exam_router
router = APIRouter(prefix="/api", tags=["Home"])
@router.get('/healthcheck')
async def healthcheck():
return {"healthy": True}
router.include_router(training_router, prefix="/training", tags=["Training"])
router.include_router(user_router, prefix="/user", tags=["Users"])
router.include_router(exam_router)

View File

@@ -0,0 +1,16 @@
from fastapi import APIRouter
from .listening import listening_router
from .reading import reading_router
from .speaking import speaking_router
from .writing import writing_router
from .level import level_router
from .grade import grade_router
exam_router = APIRouter()
exam_router.include_router(listening_router, prefix="/listening", tags=["Listening"])
exam_router.include_router(reading_router, prefix="/reading", tags=["Reading"])
exam_router.include_router(speaking_router, prefix="/speaking", tags=["Speaking"])
exam_router.include_router(writing_router, prefix="/writing", tags=["Writing"])
exam_router.include_router(level_router, prefix="/level", tags=["Level"])
exam_router.include_router(grade_router, prefix="/grade", tags=["Grade"])

View File

@@ -1,9 +1,9 @@
from dependency_injector.wiring import inject, Provide
from fastapi import APIRouter, Depends, Path, Request, BackgroundTasks
from app.controllers.abc import IGradeController
from app.dtos.writing import WritingGradeTaskDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.controllers import IGradeController
from ielts_be.dtos.writing import WritingGradeTaskDTO
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
controller = "grade_controller"
grade_router = APIRouter()

View File

@@ -1,9 +1,9 @@
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, UploadFile, Request
from app.dtos.level import LevelExercisesDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.controllers.abc import ILevelController
from ielts_be.dtos.level import LevelExercisesDTO
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.controllers import ILevelController
controller = "level_controller"
level_router = APIRouter()

View File

@@ -3,10 +3,10 @@ import random
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, Path, Query, UploadFile
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.controllers.abc import IListeningController
from app.configs.constants import EducationalContent, ListeningExerciseType
from app.dtos.listening import SaveListeningDTO, GenerateListeningExercises, Dialog
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.controllers import IListeningController
from ielts_be.configs.constants import EducationalContent
from ielts_be.dtos.listening import GenerateListeningExercises, Dialog
controller = "listening_controller"
listening_router = APIRouter()

View File

@@ -4,10 +4,10 @@ from typing import Optional
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, Path, Query, UploadFile
from app.configs.constants import EducationalContent
from app.dtos.reading import ReadingDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.controllers.abc import IReadingController
from ielts_be.configs.constants import EducationalContent
from ielts_be.dtos.reading import ReadingDTO
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.controllers import IReadingController
controller = "reading_controller"
reading_router = APIRouter()

View File

@@ -4,10 +4,10 @@ from typing import Optional
from dependency_injector.wiring import inject, Provide
from fastapi import APIRouter, Path, Query, Depends
from app.dtos.speaking import Video
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.configs.constants import EducationalContent
from app.controllers.abc import ISpeakingController
from ielts_be.dtos.speaking import Video
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.configs.constants import EducationalContent
from ielts_be.controllers import ISpeakingController
controller = "speaking_controller"
speaking_router = APIRouter()

View File

@@ -0,0 +1,42 @@
import random
from dependency_injector.wiring import inject, Provide
from fastapi import APIRouter, Path, Query, Depends, UploadFile, File
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.configs.constants import EducationalContent
from ielts_be.controllers import IWritingController
controller = "writing_controller"
writing_router = APIRouter()
@writing_router.post(
'/{task}/attachment',
dependencies=[Depends(Authorized([IsAuthenticatedViaBearerToken]))]
)
@inject
async def generate_writing_academic(
task: int = Path(..., ge=1, le=2),
file: UploadFile = File(...),
difficulty: str = Query(default=None),
writing_controller: IWritingController = Depends(Provide[controller])
):
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
return await writing_controller.get_writing_task_academic_question(task, file, difficulty)
@writing_router.get(
'/{task}',
dependencies=[Depends(Authorized([IsAuthenticatedViaBearerToken]))]
)
@inject
async def generate_writing(
task: int = Path(..., ge=1, le=2),
difficulty: str = Query(default=None),
topic: str = Query(default=None),
writing_controller: IWritingController = Depends(Provide[controller])
):
difficulty = random.choice(EducationalContent.DIFFICULTIES) if not difficulty else difficulty
topic = random.choice(EducationalContent.MTI_TOPICS) if not topic else topic
return await writing_controller.get_writing_task_general_question(task, topic, difficulty)

View File

@@ -1,9 +1,9 @@
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, Request
from app.dtos.training import FetchTipsDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.controllers.abc import ITrainingController
from ielts_be.dtos.training import FetchTipsDTO
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.controllers import ITrainingController
controller = "training_controller"
training_router = APIRouter()

View File

@@ -1,9 +1,9 @@
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends
from app.dtos.user_batch import BatchUsersDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.controllers.abc import IUserController
from ielts_be.dtos.user_batch import BatchUsersDTO
from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from ielts_be.controllers import IUserController
controller = "user_controller"
user_router = APIRouter()

View File

@@ -9,11 +9,9 @@ from httpx import AsyncClient as HTTPClient
from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer
from app.repositories.impl import *
from app.repositories.impl.document_stores.mongo import MongoDB
from app.services.impl import *
from app.controllers.impl import *
from app.services.impl.exam.evaluation import EvaluationService
from ielts_be.repositories.impl import *
from ielts_be.services.impl import *
from ielts_be.controllers.impl import *
load_dotenv()
@@ -33,7 +31,7 @@ class DependencyInjector:
self._setup_services()
self._setup_controllers()
self._container.wire(
packages=["app"]
packages=["ielts_be"]
)
return self
@@ -48,14 +46,14 @@ class DependencyInjector:
self._container.tts = providers.Factory(AWSPolly, client=self._container.polly_client)
"""
with open('app/services/impl/third_parties/elai/conf.json', 'r') as file:
with open('ielts_be/services/impl/third_parties/elai/conf.json', 'r') as file:
elai_conf = json.load(file)
with open('app/services/impl/third_parties/elai/avatars.json', 'r') as file:
with open('ielts_be/services/impl/third_parties/elai/avatars.json', 'r') as file:
elai_avatars = json.load(file)
"""
with open('app/services/impl/third_parties/heygen/avatars.json', 'r') as file:
with open('ielts_be/services/impl/third_parties/heygen/avatars.json', 'r') as file:
heygen_avatars = json.load(file)
self._container.vid_gen = providers.Factory(
@@ -99,7 +97,7 @@ class DependencyInjector:
WritingService, llm=self._container.llm, ai_detector=self._container.ai_detector
)
with open('app/services/impl/exam/level/mc_variants.json', 'r') as file:
with open('ielts_be/services/impl/exam/level/mc_variants.json', 'r') as file:
mc_variants = json.load(file)
self._container.level_service = providers.Factory(

View File

@@ -15,7 +15,7 @@
},
"filters": {
"error_and_above": {
"()": "app.configs.logging.ErrorAndAboveFilter"
"()": "ielts_be.configs.logging.ErrorAndAboveFilter"
}
},
"handlers": {
@@ -33,7 +33,7 @@
"stream": "ext://sys.stderr"
},
"queue_handler": {
"class": "app.configs.logging.QueueListenerHandler",
"class": "ielts_be.configs.logging.QueueListenerHandler",
"handlers": [
"cfg://handlers.console",
"cfg://handlers.error"

View File

@@ -4,7 +4,7 @@ from queue import Queue
import atexit
class QueueHnadlerHelper:
class QueueHandlerHelper:
@staticmethod
def resolve_handlers(l):
@@ -40,9 +40,9 @@ class QueueHnadlerHelper:
class QueueListenerHandler(QueueHandler):
def __init__(self, handlers, respect_handler_level=False, auto_run=True, queue=Queue(-1)):
queue = QueueHnadlerHelper.resolve_queue(queue)
queue = QueueHandlerHelper.resolve_queue(queue)
super().__init__(queue)
handlers = QueueHnadlerHelper.resolve_handlers(handlers)
handlers = QueueHandlerHelper.resolve_handlers(handlers)
self._listener = QueueListener(
self.queue,
*handlers,

View File

@@ -0,0 +1,3 @@
from .abc import *
__all__ = abc.__all__

View File

@@ -0,0 +1,11 @@
from .grade import IGradeController
from .training import ITrainingController
from .user import IUserController
from .exam import *
__all__ = [
"IGradeController",
"ITrainingController",
"IUserController",
]
__all__.extend(exam.__all__)

View File

@@ -1,19 +1,13 @@
from .level import ILevelController
from .listening import IListeningController
from .reading import IReadingController
from .writing import IWritingController
from .speaking import ISpeakingController
from .grade import IGradeController
from .training import ITrainingController
from .user import IUserController
__all__ = [
"IListeningController",
"IReadingController",
"IWritingController",
"ISpeakingController",
"ILevelController",
"IGradeController",
"ITrainingController",
"IUserController",
]
from .level import ILevelController
from .listening import IListeningController
from .reading import IReadingController
from .writing import IWritingController
from .speaking import ISpeakingController
__all__ = [
"IListeningController",
"IReadingController",
"IWritingController",
"ISpeakingController",
"ILevelController",
]

View File

@@ -1,7 +1,4 @@
from abc import ABC, abstractmethod
from typing import Optional
from fastapi import BackgroundTasks
class ISpeakingController(ABC):

View File

@@ -0,0 +1,14 @@
from abc import ABC, abstractmethod
from fastapi.datastructures import UploadFile
class IWritingController(ABC):
@abstractmethod
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
pass
@abstractmethod
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
pass

View File

@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Union
from typing import Dict
from fastapi import BackgroundTasks
from fastapi.datastructures import FormData

View File

@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
class IUserController(ABC):
@abstractmethod
async def batch_import(self, batch):
pass

View File

@@ -0,0 +1,12 @@
from .training import TrainingController
from .grade import GradeController
from .user import UserController
from .exam import *
__all__ = [
"TrainingController",
"GradeController",
"UserController"
]
__all__.extend(exam.__all__)

View File

@@ -1,19 +1,13 @@
from .level import LevelController
from .listening import ListeningController
from .reading import ReadingController
from .speaking import SpeakingController
from .writing import WritingController
from .training import TrainingController
from .grade import GradeController
from .user import UserController
__all__ = [
"LevelController",
"ListeningController",
"ReadingController",
"SpeakingController",
"WritingController",
"TrainingController",
"GradeController",
"UserController"
]
from .level import LevelController
from .listening import ListeningController
from .reading import ReadingController
from .speaking import SpeakingController
from .writing import WritingController
__all__ = [
"LevelController",
"ListeningController",
"ReadingController",
"SpeakingController",
"WritingController",
]

View File

@@ -1,10 +1,8 @@
from fastapi import UploadFile
from typing import Dict, Optional
from watchfiles import awatch
from app.controllers.abc import ILevelController
from app.services.abc import ILevelService
from ielts_be.controllers import ILevelController
from ielts_be.services import ILevelService
class LevelController(ILevelController):

View File

@@ -3,9 +3,9 @@ import io
from fastapi import UploadFile
from starlette.responses import StreamingResponse, Response
from app.controllers.abc import IListeningController
from app.dtos.listening import GenerateListeningExercises, Dialog
from app.services.abc import IListeningService
from ielts_be.controllers import IListeningController
from ielts_be.services import IListeningService
from ielts_be.dtos.listening import GenerateListeningExercises, Dialog
class ListeningController(IListeningController):

View File

@@ -3,9 +3,9 @@ from typing import Optional
from fastapi import UploadFile, Response
from app.controllers.abc import IReadingController
from app.dtos.reading import ReadingDTO
from app.services.abc import IReadingService
from ielts_be.controllers import IReadingController
from ielts_be.services import IReadingService
from ielts_be.dtos.reading import ReadingDTO
class ReadingController(IReadingController):

View File

@@ -1,9 +1,7 @@
import logging
import random
from typing import Optional
from app.controllers.abc import ISpeakingController
from app.services.abc import ISpeakingService, IVideoGeneratorService
from ielts_be.controllers import ISpeakingController
from ielts_be.services import ISpeakingService, IVideoGeneratorService
class SpeakingController(ISpeakingController):

View File

@@ -0,0 +1,19 @@
from fastapi import UploadFile, HTTPException
from ielts_be.controllers import IWritingController
from ielts_be.services import IWritingService
class WritingController(IWritingController):
def __init__(self, writing_service: IWritingService):
self._service = writing_service
async def get_writing_task_general_question(self, task: int, topic: str, difficulty: str):
return await self._service.get_writing_task_general_question(task, topic, difficulty)
async def get_writing_task_academic_question(self, task: int, attachment: UploadFile, difficulty: str):
if attachment.content_type not in ['image/jpeg', 'image/png']:
raise HTTPException(status_code=400, detail="Invalid file type. Only JPEG and PNG allowed.")
return await self._service.get_writing_task_academic_question(task, attachment, difficulty)

View File

@@ -1,15 +1,14 @@
import logging
from typing import Dict, List, Union
from uuid import uuid4
from typing import Dict
from fastapi import BackgroundTasks, Response, HTTPException
from fastapi.datastructures import FormData
from app.controllers.abc import IGradeController
from app.dtos.evaluation import EvaluationType
from app.dtos.speaking import GradeSpeakingItem
from app.dtos.writing import WritingGradeTaskDTO
from app.services.abc import IGradeService, IEvaluationService
from ielts_be.controllers import IGradeController
from ielts_be.services import IGradeService, IEvaluationService
from ielts_be.dtos.evaluation import EvaluationType
from ielts_be.dtos.speaking import GradeSpeakingItem
from ielts_be.dtos.writing import WritingGradeTaskDTO
class GradeController(IGradeController):
@@ -26,6 +25,9 @@ class GradeController(IGradeController):
self,
task: int, dto: WritingGradeTaskDTO, background_tasks: BackgroundTasks
):
if task == 1 and dto.type == "academic" and dto.attachment is None:
raise HTTPException(status_code=400, detail="Academic writing task requires a picture!")
await self._evaluation_service.create_evaluation(
dto.userId, dto.sessionId, dto.exerciseId, EvaluationType.WRITING, task
)

View File

@@ -1,8 +1,8 @@
from typing import Dict
from app.controllers.abc import ITrainingController
from app.dtos.training import FetchTipsDTO
from app.services.abc import ITrainingService
from ielts_be.controllers import ITrainingController
from ielts_be.services import ITrainingService
from ielts_be.dtos.training import FetchTipsDTO
class TrainingController(ITrainingController):

View File

@@ -1,6 +1,6 @@
from app.controllers.abc import IUserController
from app.dtos.user_batch import BatchUsersDTO
from app.services.abc import IUserService
from ielts_be.controllers import IUserController
from ielts_be.services import IUserService
from ielts_be.dtos.user_batch import BatchUsersDTO
class UserController(IUserController):

View File

@@ -2,7 +2,7 @@ from typing import List, Optional
from pydantic import BaseModel
from app.configs.constants import LevelExerciseType
from ielts_be.configs.constants import LevelExerciseType
class LevelExercises(BaseModel):

View File

@@ -4,7 +4,7 @@ from typing import List, Dict, Optional
from pydantic import BaseModel, Field
from app.configs.constants import MinTimers, EducationalContent, ListeningExerciseType
from ielts_be.configs.constants import MinTimers, EducationalContent, ListeningExerciseType
class SaveListeningDTO(BaseModel):

View File

@@ -3,7 +3,7 @@ from typing import List, Optional
from pydantic import BaseModel, Field
from app.configs.constants import ReadingExerciseType, EducationalContent
from ielts_be.configs.constants import ReadingExerciseType, EducationalContent
class ReadingExercise(BaseModel):
type: ReadingExerciseType

View File

@@ -1,5 +1,3 @@
from typing import List, Dict
from fastapi import UploadFile
from pydantic import BaseModel

View File

@@ -1,4 +1,3 @@
import random
from enum import Enum
from typing import Optional

View File

@@ -1,3 +1,5 @@
from typing import Optional
from pydantic import BaseModel
@@ -7,3 +9,5 @@ class WritingGradeTaskDTO(BaseModel):
exerciseId: str
question: str
answer: str
type: str
attachment: Optional[str]

View File

@@ -117,3 +117,9 @@ class FileHelper:
await file.write(file_bytes)
return ext, path_id
@staticmethod
async def encode_image(image_path: str) -> str:
async with aiofiles.open(image_path, "rb") as image_file:
img = await image_file.read()
return base64.b64encode(img).decode('utf-8')

View File

@@ -2,12 +2,12 @@ from typing import Dict, Any
from pydantic import ValidationError
from app.dtos.exams.level import (
from ielts_be.dtos.exams.level import (
MultipleChoiceExercise,
FillBlanksExercise,
Part, Exam, Text
)
from app.dtos.sheet import Sheet, Option, MultipleChoiceQuestion, FillBlanksWord
from ielts_be.dtos.sheet import Sheet, Option, MultipleChoiceQuestion, FillBlanksWord
class LevelMapper:

View File

@@ -2,7 +2,7 @@ from typing import Dict, Any, List, Union, Optional
from pydantic import BaseModel
from app.dtos.exams.listening import (
from ielts_be.dtos.exams.listening import (
TrueFalseExercise,
MultipleChoiceExercise,
WriteBlanksExercise,

View File

@@ -1,6 +1,6 @@
from typing import Dict, Any
from app.dtos.exams.reading import (
from ielts_be.dtos.exams.reading import (
Part, Exam, Context, FillBlanksExercise,
TrueFalseExercise, MatchSentencesExercise,
WriteBlanksExercise, MultipleChoice

View File

@@ -5,7 +5,7 @@ from fastapi import Request
from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.security.base import SecurityBase
from app.exceptions import CustomException, UnauthorizedException
from ielts_be.exceptions import CustomException, UnauthorizedException
class BaseAuthorization(ABC):

View File

@@ -0,0 +1,3 @@
from .abc import *
__all__ = abc.__all__

View File

@@ -0,0 +1,6 @@
from .document_stores import *
from .file_storage import *
__all__ = []
__all__.extend(document_stores.__all__)
__all__.extend(file_storage.__all__)

View File

@@ -1,7 +1,7 @@
from .firestore import Firestore
#from .mongo import MongoDB
from .mongo import MongoDB
__all__ = [
"Firestore",
#"MongoDB"
"MongoDB"
]

View File

@@ -4,7 +4,7 @@ from typing import Optional, List, Dict
from google.cloud.firestore_v1.async_client import AsyncClient
from google.cloud.firestore_v1.async_collection import AsyncCollectionReference
from google.cloud.firestore_v1.async_document import AsyncDocumentReference
from app.repositories.abc import IDocumentStore
from ielts_be.repositories import IDocumentStore
class Firestore(IDocumentStore):

View File

@@ -4,7 +4,7 @@ from typing import Optional, List, Dict
from motor.motor_asyncio import AsyncIOMotorDatabase
from app.repositories.abc import IDocumentStore
from ielts_be.repositories import IDocumentStore
class MongoDB(IDocumentStore):

View File

@@ -4,7 +4,7 @@ from typing import Optional
import aiofiles
from httpx import AsyncClient
from app.repositories.abc import IFileStorage
from ielts_be.repositories import IFileStorage
class FirebaseStorage(IFileStorage):

View File

@@ -0,0 +1,3 @@
from .abc import *
__all__ = abc.__all__

View File

@@ -1,9 +1,8 @@
from abc import abstractmethod, ABC
from typing import Union, List, Dict
from fastapi import BackgroundTasks
from app.dtos.evaluation import EvaluationType
from ielts_be.dtos.evaluation import EvaluationType
class IEvaluationService(ABC):

View File

@@ -1,12 +1,7 @@
from abc import ABC, abstractmethod
import random
from typing import Dict, Optional
from fastapi import UploadFile
from app.configs.constants import EducationalContent
class ILevelService(ABC):

Some files were not shown because too many files have changed in this diff Show More