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 # For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available. # 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. # 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): def main(env: str):
uvicorn.run( uvicorn.run(
app="app.server:app", app="ielts_be:app",
host="localhost", host="localhost",
port=8000, port=8000,
reload=True if env != "production" else False, 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 json
import os import os
import pathlib import pathlib
import logging.config import logging.config
import logging.handlers import logging.handlers
import aioboto3 import aioboto3
import contextlib import contextlib
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from collections import defaultdict from collections import defaultdict
from typing import List from typing import List
from http import HTTPStatus from http import HTTPStatus
import httpx import httpx
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from fastapi.middleware import Middleware from fastapi.middleware import Middleware
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
import nltk import nltk
from starlette import status from starlette import status
from app.api import router from ielts_be.api import router
from app.configs import DependencyInjector from ielts_be.configs import DependencyInjector
from app.exceptions import CustomException from ielts_be.exceptions import CustomException
from app.middlewares import AuthenticationMiddleware, AuthBackend from ielts_be.middlewares import AuthenticationMiddleware, AuthBackend
from app.services.impl import OpenAIWhisper from ielts_be.services.impl import OpenAIWhisper
@asynccontextmanager @asynccontextmanager
async def lifespan(_app: FastAPI): async def lifespan(_app: FastAPI):
""" """
Startup and Shutdown logic is in this lifespan method Startup and Shutdown logic is in this lifespan method
https://fastapi.tiangolo.com/advanced/events/ https://fastapi.tiangolo.com/advanced/events/
""" """
# NLTK required datasets download # NLTK required datasets download
nltk.download('words') nltk.download('words')
nltk.download("punkt") nltk.download("punkt")
# AWS Polly client instantiation # AWS Polly client instantiation
context_stack = contextlib.AsyncExitStack() context_stack = contextlib.AsyncExitStack()
session = aioboto3.Session() session = aioboto3.Session()
polly_client = await context_stack.enter_async_context( polly_client = await context_stack.enter_async_context(
session.client( session.client(
'polly', 'polly',
region_name='eu-west-1', region_name='eu-west-1',
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"), aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID") aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID")
) )
) )
http_client = httpx.AsyncClient() http_client = httpx.AsyncClient()
stt = OpenAIWhisper() stt = OpenAIWhisper()
DependencyInjector( DependencyInjector(
polly_client, polly_client,
http_client, http_client,
stt stt
).inject() ).inject()
# Setup logging # Setup logging
config_file = pathlib.Path("./app/configs/logging/logging_config.json") config_file = pathlib.Path("./ielts_be/configs/logging/logging_config.json")
with open(config_file) as f_in: with open(config_file) as f_in:
config = json.load(f_in) config = json.load(f_in)
logging.config.dictConfig(config) logging.config.dictConfig(config)
yield yield
stt.close() stt.close()
await http_client.aclose() await http_client.aclose()
await polly_client.close() await polly_client.close()
await context_stack.aclose() await context_stack.aclose()
def setup_listeners(_app: FastAPI) -> None: def setup_listeners(_app: FastAPI) -> None:
@_app.exception_handler(RequestValidationError) @_app.exception_handler(RequestValidationError)
async def custom_form_validation_error(request, exc): async def custom_form_validation_error(request, exc):
""" """
Don't delete request param Don't delete request param
""" """
reformatted_message = defaultdict(list) reformatted_message = defaultdict(list)
for pydantic_error in exc.errors(): for pydantic_error in exc.errors():
loc, msg = pydantic_error["loc"], pydantic_error["msg"] loc, msg = pydantic_error["loc"], pydantic_error["msg"]
filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc filtered_loc = loc[1:] if loc[0] in ("body", "query", "path") else loc
field_string = ".".join(filtered_loc) field_string = ".".join(filtered_loc)
if field_string == "cookie.refresh_token": if field_string == "cookie.refresh_token":
return JSONResponse( return JSONResponse(
status_code=401, status_code=401,
content={"error_code": 401, "message": HTTPStatus.UNAUTHORIZED.description}, content={"error_code": 401, "message": HTTPStatus.UNAUTHORIZED.description},
) )
reformatted_message[field_string].append(msg) reformatted_message[field_string].append(msg)
return JSONResponse( return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
content=jsonable_encoder( content=jsonable_encoder(
{"details": "Invalid request!", "errors": reformatted_message} {"details": "Invalid request!", "errors": reformatted_message}
), ),
) )
@_app.exception_handler(CustomException) @_app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException): async def custom_exception_handler(request: Request, exc: CustomException):
""" """
Don't delete request param Don't delete request param
""" """
return JSONResponse( return JSONResponse(
status_code=exc.code, status_code=exc.code,
content={"error_code": exc.error_code, "message": exc.message}, content={"error_code": exc.error_code, "message": exc.message},
) )
@_app.exception_handler(Exception) @_app.exception_handler(Exception)
async def default_exception_handler(request: Request, exc: Exception): async def default_exception_handler(request: Request, exc: Exception):
""" """
Don't delete request param Don't delete request param
""" """
return JSONResponse( return JSONResponse(
status_code=500, status_code=500,
content=str(exc), content=str(exc),
) )
def setup_middleware() -> List[Middleware]: def setup_middleware() -> List[Middleware]:
middleware = [ middleware = [
Middleware( Middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=["*"],
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
), ),
Middleware( Middleware(
AuthenticationMiddleware, AuthenticationMiddleware,
backend=AuthBackend() backend=AuthBackend()
) )
] ]
return middleware return middleware
def create_app() -> FastAPI: def create_app() -> FastAPI:
env = os.getenv("ENV") env = os.getenv("ENV")
_app = FastAPI( _app = FastAPI(
docs_url="/docs" if env != "production" else None, docs_url="/docs" if env != "production" else None,
redoc_url="/redoc" if env != "production" else None, redoc_url="/redoc" if env != "production" else None,
middleware=setup_middleware(), middleware=setup_middleware(),
lifespan=lifespan lifespan=lifespan
) )
_app.include_router(router) _app.include_router(router)
setup_listeners(_app) setup_listeners(_app)
return _app return _app
app = create_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 dependency_injector.wiring import inject, Provide
from fastapi import APIRouter, Depends, Path, Request, BackgroundTasks from fastapi import APIRouter, Depends, Path, Request, BackgroundTasks
from app.controllers.abc import IGradeController from ielts_be.controllers import IGradeController
from app.dtos.writing import WritingGradeTaskDTO from ielts_be.dtos.writing import WritingGradeTaskDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
controller = "grade_controller" controller = "grade_controller"
grade_router = APIRouter() grade_router = APIRouter()

View File

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

View File

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

View File

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

View File

@@ -4,10 +4,10 @@ from typing import Optional
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import inject, Provide
from fastapi import APIRouter, Path, Query, Depends from fastapi import APIRouter, Path, Query, Depends
from app.dtos.speaking import Video from ielts_be.dtos.speaking import Video
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.configs.constants import EducationalContent from ielts_be.configs.constants import EducationalContent
from app.controllers.abc import ISpeakingController from ielts_be.controllers import ISpeakingController
controller = "speaking_controller" controller = "speaking_controller"
speaking_router = APIRouter() 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 dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from app.dtos.training import FetchTipsDTO from ielts_be.dtos.training import FetchTipsDTO
from app.middlewares import Authorized, IsAuthenticatedViaBearerToken from ielts_be.middlewares import Authorized, IsAuthenticatedViaBearerToken
from app.controllers.abc import ITrainingController from ielts_be.controllers import ITrainingController
controller = "training_controller" controller = "training_controller"
training_router = APIRouter() training_router = APIRouter()

View File

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

View File

@@ -9,11 +9,9 @@ from httpx import AsyncClient as HTTPClient
from dotenv import load_dotenv from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer from sentence_transformers import SentenceTransformer
from app.repositories.impl import * from ielts_be.repositories.impl import *
from app.repositories.impl.document_stores.mongo import MongoDB from ielts_be.services.impl import *
from app.services.impl import * from ielts_be.controllers.impl import *
from app.controllers.impl import *
from app.services.impl.exam.evaluation import EvaluationService
load_dotenv() load_dotenv()
@@ -33,7 +31,7 @@ class DependencyInjector:
self._setup_services() self._setup_services()
self._setup_controllers() self._setup_controllers()
self._container.wire( self._container.wire(
packages=["app"] packages=["ielts_be"]
) )
return self return self
@@ -48,14 +46,14 @@ class DependencyInjector:
self._container.tts = providers.Factory(AWSPolly, client=self._container.polly_client) 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) 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) 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) heygen_avatars = json.load(file)
self._container.vid_gen = providers.Factory( self._container.vid_gen = providers.Factory(
@@ -99,7 +97,7 @@ class DependencyInjector:
WritingService, llm=self._container.llm, ai_detector=self._container.ai_detector 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) mc_variants = json.load(file)
self._container.level_service = providers.Factory( self._container.level_service = providers.Factory(

View File

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

View File

@@ -4,7 +4,7 @@ from queue import Queue
import atexit import atexit
class QueueHnadlerHelper: class QueueHandlerHelper:
@staticmethod @staticmethod
def resolve_handlers(l): def resolve_handlers(l):
@@ -40,9 +40,9 @@ class QueueHnadlerHelper:
class QueueListenerHandler(QueueHandler): class QueueListenerHandler(QueueHandler):
def __init__(self, handlers, respect_handler_level=False, auto_run=True, queue=Queue(-1)): 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) super().__init__(queue)
handlers = QueueHnadlerHelper.resolve_handlers(handlers) handlers = QueueHandlerHelper.resolve_handlers(handlers)
self._listener = QueueListener( self._listener = QueueListener(
self.queue, self.queue,
*handlers, *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 .level import ILevelController
from .listening import IListeningController from .listening import IListeningController
from .reading import IReadingController from .reading import IReadingController
from .writing import IWritingController from .writing import IWritingController
from .speaking import ISpeakingController from .speaking import ISpeakingController
from .grade import IGradeController
from .training import ITrainingController __all__ = [
from .user import IUserController "IListeningController",
"IReadingController",
__all__ = [ "IWritingController",
"IListeningController", "ISpeakingController",
"IReadingController", "ILevelController",
"IWritingController", ]
"ISpeakingController",
"ILevelController",
"IGradeController",
"ITrainingController",
"IUserController",
]

View File

@@ -1,7 +1,4 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Optional
from fastapi import BackgroundTasks
class ISpeakingController(ABC): 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 abc import ABC, abstractmethod
from typing import Dict, List, Union from typing import Dict
from fastapi import BackgroundTasks from fastapi import BackgroundTasks
from fastapi.datastructures import FormData 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 .level import LevelController
from .listening import ListeningController from .listening import ListeningController
from .reading import ReadingController from .reading import ReadingController
from .speaking import SpeakingController from .speaking import SpeakingController
from .writing import WritingController from .writing import WritingController
from .training import TrainingController
from .grade import GradeController __all__ = [
from .user import UserController "LevelController",
"ListeningController",
__all__ = [ "ReadingController",
"LevelController", "SpeakingController",
"ListeningController", "WritingController",
"ReadingController", ]
"SpeakingController",
"WritingController",
"TrainingController",
"GradeController",
"UserController"
]

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,7 @@
import logging import logging
import random
from typing import Optional
from app.controllers.abc import ISpeakingController from ielts_be.controllers import ISpeakingController
from app.services.abc import ISpeakingService, IVideoGeneratorService from ielts_be.services import ISpeakingService, IVideoGeneratorService
class SpeakingController(ISpeakingController): 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 import logging
from typing import Dict, List, Union from typing import Dict
from uuid import uuid4
from fastapi import BackgroundTasks, Response, HTTPException from fastapi import BackgroundTasks, Response, HTTPException
from fastapi.datastructures import FormData from fastapi.datastructures import FormData
from app.controllers.abc import IGradeController from ielts_be.controllers import IGradeController
from app.dtos.evaluation import EvaluationType from ielts_be.services import IGradeService, IEvaluationService
from app.dtos.speaking import GradeSpeakingItem from ielts_be.dtos.evaluation import EvaluationType
from app.dtos.writing import WritingGradeTaskDTO from ielts_be.dtos.speaking import GradeSpeakingItem
from app.services.abc import IGradeService, IEvaluationService from ielts_be.dtos.writing import WritingGradeTaskDTO
class GradeController(IGradeController): class GradeController(IGradeController):
@@ -26,6 +25,9 @@ class GradeController(IGradeController):
self, self,
task: int, dto: WritingGradeTaskDTO, background_tasks: BackgroundTasks 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( await self._evaluation_service.create_evaluation(
dto.userId, dto.sessionId, dto.exerciseId, EvaluationType.WRITING, task dto.userId, dto.sessionId, dto.exerciseId, EvaluationType.WRITING, task
) )

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ from typing import List, Dict, Optional
from pydantic import BaseModel, Field 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): class SaveListeningDTO(BaseModel):

View File

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

View File

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

View File

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

View File

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

View File

@@ -117,3 +117,9 @@ class FileHelper:
await file.write(file_bytes) await file.write(file_bytes)
return ext, path_id 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 pydantic import ValidationError
from app.dtos.exams.level import ( from ielts_be.dtos.exams.level import (
MultipleChoiceExercise, MultipleChoiceExercise,
FillBlanksExercise, FillBlanksExercise,
Part, Exam, Text Part, Exam, Text
) )
from app.dtos.sheet import Sheet, Option, MultipleChoiceQuestion, FillBlanksWord from ielts_be.dtos.sheet import Sheet, Option, MultipleChoiceQuestion, FillBlanksWord
class LevelMapper: class LevelMapper:

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ from fastapi import Request
from fastapi.openapi.models import APIKey, APIKeyIn from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
from app.exceptions import CustomException, UnauthorizedException from ielts_be.exceptions import CustomException, UnauthorizedException
class BaseAuthorization(ABC): 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 .firestore import Firestore
#from .mongo import MongoDB from .mongo import MongoDB
__all__ = [ __all__ = [
"Firestore", "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_client import AsyncClient
from google.cloud.firestore_v1.async_collection import AsyncCollectionReference from google.cloud.firestore_v1.async_collection import AsyncCollectionReference
from google.cloud.firestore_v1.async_document import AsyncDocumentReference from google.cloud.firestore_v1.async_document import AsyncDocumentReference
from app.repositories.abc import IDocumentStore from ielts_be.repositories import IDocumentStore
class Firestore(IDocumentStore): class Firestore(IDocumentStore):

View File

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

View File

@@ -4,7 +4,7 @@ from typing import Optional
import aiofiles import aiofiles
from httpx import AsyncClient from httpx import AsyncClient
from app.repositories.abc import IFileStorage from ielts_be.repositories import IFileStorage
class FirebaseStorage(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 abc import abstractmethod, ABC
from typing import Union, List, Dict
from fastapi import BackgroundTasks from fastapi import BackgroundTasks
from app.dtos.evaluation import EvaluationType from ielts_be.dtos.evaluation import EvaluationType
class IEvaluationService(ABC): class IEvaluationService(ABC):

View File

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

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