added core service

parent b3e45ed3
FROM python:3.10-slim-buster
RUN pip3 install --upgrade pip && pip3 install poetry
WORKDIR /app
COPY pyproject.toml poetry.lock /app/
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi
COPY . .
version: '3'
services:
core:
build:
context: .
dockerfile: Dockerfile
image: core
container_name: core
restart: always
ports:
- "8000:8000"
env_file:
- .env
volumes:
- .:/app
command: bash -c "poetry run python3 src/app/main.py"
depends_on:
- core-db
core-db:
image: postgres:14-alpine
container_name: core-pg
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
ports:
- "5437:5432"
volumes:
- ./data/postgres:/var/lib/postgresql/data/
networks:
default:
name: fa-2-exam-network
This diff is collapsed.
[tool.poetry]
name = "core"
version = "0.1.0"
description = ""
authors = ["beka <beka1990.30.10@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.109.0"
uvicorn = "^0.25.0"
tortoise-orm = "^0.20.0"
aerich = "^0.7.2"
asyncpg = "^0.29.0"
httpx = "^0.26.0"
[tool.aerich]
tortoise_orm = "src.app.db.conf.TORTOISE_ORM"
location = "src/migrations"
src_folder = "./."
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
from uuid import UUID
from starlette import status
import fastapi
from tortoise.exceptions import ValidationError
from base_ctrl import BaseController
from exceptions import common as common_exc, http as http_exc
from .services import ChapterService
class ChapterController(BaseController):
chapter_service = ChapterService()
async def get_list(self, **kwargs):
return await self.chapter_service.get_chapter_list(**kwargs)
async def get(self, id: UUID):
try:
return await self.chapter_service.get_chapter(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **kwargs):
try:
return await self.chapter_service.create_chapter(**kwargs)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except ValidationError as e:
raise http_exc.HTTPBadRequestException(detail=f"Validation error: {str(e)}")
async def update(self, id: UUID, **kwargs):
try:
return await self.chapter_service.update_chapter(id, **kwargs)
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: UUID):
try:
await self.chapter_service.delete_chapter(id)
return fastapi.responses.Response(status_code=status.HTTP_204_NO_CONTENT)
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
from uuid import UUID
import fastapi
from .schemas import ChapterBaseSchema
from .ctrl import ChapterController
router = fastapi.APIRouter(prefix='/chapters', tags=['Chapter'])
ctrl = ChapterController()
@router.get("")
async def get_chapters(
query: ChapterBaseSchema = fastapi.Depends(),
):
return await ctrl.get_list(**query.model_dump(exclude_none=True))
@router.get('/{id}')
async def get_chapter(
id: UUID,
):
return await ctrl.get(id)
@router.post('')
async def create_chapter(
body: ChapterBaseSchema,
):
return await ctrl.create(**body.model_dump(exclude_none=True))
@router.patch('/{id}')
async def update_chapter(
id: UUID, body: ChapterBaseSchema,
):
return await ctrl.update(id, **body.model_dump(exclude_none=True))
@router.delete('/{id}')
async def delete_chapter(
id: UUID,
):
return await ctrl.delete(id)
from uuid import UUID
from pydantic import BaseModel, ConfigDict
class ChapterBaseSchema(BaseModel):
name: str | None = None
class ChapterIdSchema(BaseModel):
id: UUID
model_config = ConfigDict(from_attributes=True)
class ChapterSchema(ChapterBaseSchema, ChapterIdSchema):
pass
from uuid import UUID
from db.repositories import ChapterRepository
from .schemas import ChapterSchema
class ChapterService:
chapter_repository = ChapterRepository()
async def get_chapter_list(self, **kwargs):
chapters = await self.chapter_repository.get_list(**kwargs)
return [ChapterSchema.model_validate(chapter) for chapter in chapters]
async def get_chapter(self, id: UUID):
chapter = await self.chapter_repository.get(id)
return ChapterSchema.model_validate(chapter)
async def create_chapter(self, **kwargs):
chapter = await self.chapter_repository.create(**kwargs)
return ChapterSchema.model_validate(chapter)
async def update_chapter(self, id: UUID, **kwargs):
chapter = await self.chapter_repository.update(id, **kwargs)
return ChapterSchema.model_validate(chapter)
async def delete_chapter(self, id: UUID):
return await self.chapter_repository.delete(id)
import httpx
import fastapi as fa
async def get_current_user(token: str):
auth_service_url = "http://auth:8000/api/auth/check_token"
async with httpx.AsyncClient() as client:
data = {"token": token}
response = await client.post(auth_service_url, json=data)
if response.status_code == 200:
result = response.json()
if result.get("success"):
return result
else:
raise fa.HTTPException(
status_code=401,
detail="Unauthorized: Invalid token",
)
else:
raise fa.HTTPException(
status_code=fa.status.HTTP_400_BAD_REQUEST,
detail="Error while fetching user data",
)
from uuid import UUID
from starlette import status
import fastapi
from tortoise.exceptions import ValidationError
from base_ctrl import BaseController
from exceptions import common as common_exc, http as http_exc
from .services import MaterialService
class MaterialController(BaseController):
material_service = MaterialService()
async def get_list(self, **kwargs):
return await self.material_service.get_material_list(**kwargs)
async def get(self, id: UUID):
try:
return await self.material_service.get_material(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **kwargs):
try:
return await self.material_service.create_material(**kwargs)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except ValidationError as e:
raise http_exc.HTTPBadRequestException(detail=f"Validation error: {str(e)}")
async def update(self, id: UUID, **kwargs):
try:
return await self.material_service.update_material(id, **kwargs)
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: UUID):
try:
await self.material_service.delete_material(id)
return fastapi.responses.Response(status_code=status.HTTP_204_NO_CONTENT)
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def set_material_view(self, **kwargs):
try:
return await self.material_service.set_material_view(**kwargs)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except ValidationError as e:
raise http_exc.HTTPBadRequestException(detail=f"Validation error: {str(e)}")
from uuid import UUID
import fastapi
from .schemas import MaterialBaseSchema, MaterialPostSchema, MaterialSetSchema
from .ctrl import MaterialController
from ..dependencies import get_current_user
router = fastapi.APIRouter(prefix='/materials', tags=['Material'])
ctrl = MaterialController()
@router.get("")
async def get_materials(
query: MaterialBaseSchema = fastapi.Depends(),
):
return await ctrl.get_list(**query.model_dump(exclude_none=True))
@router.get('/{id}')
async def get_material(
id: UUID,
):
return await ctrl.get(id)
@router.post('')
async def create_material(
body: MaterialPostSchema,
):
return await ctrl.create(**body.model_dump(exclude_none=True))
@router.patch('/{id}')
async def update_material(
id: UUID, body: MaterialPostSchema,
):
return await ctrl.update(id, **body.model_dump(exclude_none=True))
@router.delete('/{id}')
async def delete_material(
id: UUID,
):
return await ctrl.delete(id)
@router.post('view_material')
async def set_view_material(
token: str,
body: MaterialSetSchema
):
token_is_valid = await get_current_user(token)
if token_is_valid:
return await ctrl.create(**body.model_dump(exclude_none=True))
from uuid import UUID
from pydantic import BaseModel, ConfigDict
class MaterialBaseSchema(BaseModel):
name: str | None = None
text: str | None = None
chapter_id: UUID | None = None
class MaterialIdSchema(BaseModel):
id: UUID
model_config = ConfigDict(from_attributes=True)
class MaterialSchema(MaterialBaseSchema, MaterialIdSchema):
pass
class MaterialPostSchema(MaterialBaseSchema):
chapter_id: UUID | None = None
class MaterialSetSchema(BaseModel):
material_id: UUID
from uuid import UUID
from db.repositories import MaterialRepository, ChapterRepository, UserMaterialViewRepository
from .schemas import MaterialSchema
from exceptions import common as common_exc, http as http_exc
class MaterialService:
material_repository = MaterialRepository()
chapter_repository = ChapterRepository()
user_view_repo = UserMaterialViewRepository()
async def get_material_list(self, **kwargs):
materials = await self.material_repository.get_list(**kwargs)
return [MaterialSchema.model_validate(material) for material in materials]
async def get_material(self, id: UUID):
material = await self.material_repository.get(id)
return MaterialSchema.model_validate(material)
async def create_material(self, **kwargs):
chapter_uuid = kwargs.get('chapter_id')
if chapter_uuid is None:
raise http_exc.HTTPBadRequestException(detail="Validation error: Chapter must not be None")
try:
chapter = await self.chapter_repository.get(chapter_uuid)
except common_exc.NotFoundException:
raise http_exc.HTTPBadRequestException(detail="Chapter does not exist")
material = await self.material_repository.create(
name=kwargs.get('name'),
text=kwargs.get('text'),
chapter=chapter,
)
return MaterialSchema.model_validate(material)
async def update_material(self, id: UUID, **kwargs):
chapter_uuid = kwargs.get('chapter_id')
if chapter_uuid:
try:
kwargs['chapter'] = await self.chapter_repository.get(chapter_uuid)
except common_exc.NotFoundException:
raise http_exc.HTTPBadRequestException(detail='Chapter does not exist')
material = await self.material_repository.update(id, **kwargs)
return MaterialSchema.model_validate(material)
async def delete_material(self, id: UUID):
return await self.material_repository.delete(id)
async def set_material_view(self, user, **kwargs):
material_uuid = kwargs.get('material_id')
print(kwargs)
if material_uuid is None:
raise http_exc.HTTPBadRequestException(detail="Validation error: Material must not be None")
try:
material = await self.material_repository.get(material_uuid)
except common_exc.NotFoundException:
raise http_exc.HTTPBadRequestException(detail="Material does not exist")
view = self.user_view_repo.create(user, material)
return {'detail': 'success'}
from fastapi import APIRouter
from .chapter.routes import router as chapter_route
from .material.routes import router as material_route
router = APIRouter(prefix='/api')
router.include_router(chapter_route)
router.include_router(material_route)
from exceptions import common as common_exc, http as http_exc
class BaseController:
repo = None
async def get_list(self, **kwargs) -> list[dict]:
return await self.repo.get_list(**kwargs)
async def get(self, id: int) -> dict:
try:
return await self.repo.get(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **data) -> dict:
try:
return await self.repo.create(**data)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def update(self, id: int, **kwargs) -> dict:
try:
return await self.repo.update(id, **kwargs)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: int) -> None:
try:
return await self.repo.delete(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
from uuid import uuid4
from tortoise import fields, models
class IdMixin(models.Model):
id = fields.UUIDField(pk=True, default=uuid4)
class Meta:
abstract = True
class TimestampMixin(models.Model):
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
class Meta:
abstract = True
class BaseModel(IdMixin, TimestampMixin):
class Meta:
ordering = ('-created_at', )
import os
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
TORTOISE_ORM = {
'connections': {'default': os.environ.get('DATABASE_URL')},
'apps': {
'models': {
'models': ['src.app.db.models', 'aerich.models'],
'default_connection': 'default',
},
},
}
def init_db(app: FastAPI) -> None:
register_tortoise(
app,
db_url=os.environ.get('DATABASE_URL'),
modules={"models": ["db.models"]},
generate_schemas=False,
add_exception_handlers=True,
)
from tortoise import fields, models
from .base import BaseModel
class Chapter(BaseModel):
name = fields.CharField(max_length=100)
class Meta(BaseModel.Meta):
table = 'chapters'
class Material(BaseModel):
chapter = fields.ForeignKeyField(
'models.Chapter', on_delete=fields.CASCADE, related_name='materials'
)
name = fields.CharField(max_length=100)
text = fields.TextField()
class Meta(BaseModel.Meta):
table = 'materials'
class UserMaterialView(models.Model):
user_id = fields.IntField()
material = fields.ForeignKeyField('models.Material', related_name='user_views')
class Meta:
table = 'users_materials'
class Test(BaseModel):
user_id = fields.IntField()
material = fields.ForeignKeyField('models.Material', related_name='material_tests')
percent = fields.IntField()
class Meta:
table = 'tests'
import tortoise
from .models import Chapter, Material, UserMaterialView
from exceptions import common as common_exc
class BaseRepository:
model: tortoise.Model
async def get_list(self, **kwargs) -> list[tortoise.Model]:
return await self.model.filter(**kwargs)
async def get(self, id: int) -> tortoise.Model:
try:
return await self.model.get(id=id)
except tortoise.exceptions.DoesNotExist as e:
raise common_exc.NotFoundException(str(e))
async def create(self, **kwargs) -> tortoise.Model:
try:
return await self.model.create(**kwargs)
except tortoise.exceptions.IntegrityError as e:
raise common_exc.CreateException(str(e))
async def update(self, id: int, **kwargs) -> tortoise.Model:
try:
instance = await self.get(id=id)
await instance.update_from_dict(kwargs).save()
return instance
except tortoise.exceptions.IntegrityError as e:
raise common_exc.UpdateException(str(e))
async def delete(self, id: int) -> None:
try:
instance = await self.get(id=id)
await instance.delete()
except tortoise.exceptions.IntegrityError as e:
raise common_exc.DeleteException(str(e))
class ChapterRepository(BaseRepository):
model = Chapter
class MaterialRepository(BaseRepository):
model = Material
class UserMaterialViewRepository(BaseRepository):
model = UserMaterialView
class NotFoundException(Exception):
pass
class CreateException(Exception):
pass
class UpdateException(Exception):
pass
class DeleteException(Exception):
pass
import fastapi
import pydantic
from starlette import status
from fastapi.responses import JSONResponse
from .http import BaseHTTPException
async def query_params_exc_handler(
request: fastapi.Request, exc: pydantic.ValidationError,
) -> JSONResponse:
return JSONResponse(
{'detail': exc.errors()}, status.HTTP_422_UNPROCESSABLE_ENTITY,
)
async def request_exc_handler(
request: fastapi.Request, exc: BaseHTTPException,
) -> JSONResponse:
return JSONResponse(
{'detail': exc.detail}, exc.status_code,
)
async def internal_exc_handler(
request: fastapi.Request, exc: Exception,
) -> JSONResponse:
return JSONResponse(
{'detail': 'Internal Server Error'},
status.HTTP_500_INTERNAL_SERVER_ERROR,
)
from typing import Any, Dict
from fastapi.exceptions import HTTPException
from starlette import status
class BaseHTTPException(HTTPException):
pass
class HTTPBadRequestException(BaseHTTPException):
def __init__(
self, status_code: int = status.HTTP_400_BAD_REQUEST, detail: Any = None,
headers: Dict[str, str] | None = None
) -> None:
super().__init__(status_code, detail, headers)
class HTTPNotFoundException(BaseHTTPException):
def __init__(
self, status_code: int = status.HTTP_404_NOT_FOUND, detail: Any = None,
headers: Dict[str, str] | None = None
) -> None:
super().__init__(status_code, detail, headers)
import fastapi
import pydantic
from api.router import router
from exceptions import handlers as exc_handlers, http as http_exc
from db.conf import init_db
def setup():
app = fastapi.FastAPI()
app.include_router(router)
app.exception_handler(pydantic.ValidationError)(exc_handlers.query_params_exc_handler)
app.exception_handler(http_exc.BaseHTTPException)(exc_handlers.request_exc_handler)
app.exception_handler(500)(exc_handlers.internal_exc_handler)
return app
app = setup()
init_db(app)
if __name__ == '__main__':
import uvicorn
uvicorn.run('main:app', host='0.0.0.0', port=8000, reload=True)
import os
import pydantic
database_url = pydantic.PostgresDsn(os.getenv('DATABASE_URL'))
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "basemodel" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS "chapters" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS "materials" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(100) NOT NULL,
"text" TEXT NOT NULL,
"chapter_id" UUID NOT NULL REFERENCES "chapters" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "aerich" (
"id" SERIAL NOT NULL PRIMARY KEY,
"version" VARCHAR(255) NOT NULL,
"app" VARCHAR(100) NOT NULL,
"content" JSONB NOT NULL
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "users_materials" (
"id" SERIAL NOT NULL PRIMARY KEY,
"user_id" INT NOT NULL,
"material_id" UUID NOT NULL REFERENCES "materials" ("id") ON DELETE CASCADE
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
DROP TABLE IF EXISTS "users_materials";"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "tests" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" INT NOT NULL,
"percent" INT NOT NULL,
"material_id" UUID NOT NULL REFERENCES "materials" ("id") ON DELETE CASCADE
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
DROP TABLE IF EXISTS "tests";"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment