Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 351001/Budnikov/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14.3
Empty file added 351001/Budnikov/README.md
Empty file.
15 changes: 15 additions & 0 deletions 351001/Budnikov/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "budnikov"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14.3"
dependencies = [
"fastapi>=0.135.3",
"tortoise-orm>=1.1.0",
"uvicorn>=0.44.0",
]

[tool.ruff]
line-length = 120

Empty file added 351001/Budnikov/src/__init__.py
Empty file.
Empty file.
Empty file.
Empty file.
49 changes: 49 additions & 0 deletions 351001/Budnikov/src/api/v1/endpoints/editors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from fastapi import APIRouter, status
from src.schemas.dto import EditorRequestTo, EditorResponseTo
from src.dependencies.services import EditorServiceDep


router = APIRouter(
prefix="/editors",
)


@router.post(
path="",
response_model=EditorResponseTo,
status_code=status.HTTP_201_CREATED,
)
async def create_editor(editor_in: EditorRequestTo, editor_service: EditorServiceDep):
return await editor_service.create(editor_in)


@router.get(
path="",
response_model=list[EditorResponseTo],
status_code=status.HTTP_200_OK,
)
async def get_editors(editor_service: EditorServiceDep):
return await editor_service.get_all()


@router.get(
path="/{id}",
response_model=EditorResponseTo,
status_code=status.HTTP_200_OK,
)
async def get_editor(id: int, editor_service: EditorServiceDep):
return await editor_service.get_by_id(id)


@router.put(
path="/{id}",
response_model=EditorResponseTo,
status_code=status.HTTP_200_OK,
)
async def update_editor(id: int, editor_in: EditorRequestTo, editor_service: EditorServiceDep):
return await editor_service.update(id, editor_in)


@router.delete(path="/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_editor(id: int, editor_service: EditorServiceDep):
await editor_service.delete(id)
56 changes: 56 additions & 0 deletions 351001/Budnikov/src/api/v1/endpoints/issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from fastapi import APIRouter, status, Query

from src.schemas.dto import IssueRequestTo, IssueResponseTo, EditorResponseTo, LabelResponseTo, PostResponseTo
from src.dependencies.services import IssueServiceDep


router = APIRouter(prefix="/issues")


@router.post(path="", response_model=IssueResponseTo, status_code=status.HTTP_201_CREATED)
async def create_issue(issue_in: IssueRequestTo, issue_service: IssueServiceDep):
return await issue_service.create(issue_in)


@router.get(path="", response_model=list[IssueResponseTo], status_code=status.HTTP_200_OK)
async def get_issues(
issue_service: IssueServiceDep,
label_names: list[str] | None = Query(None),
label_ids: list[int] | None = Query(None),
editor_login: str | None = Query(None),
title: str | None = Query(None),
content: str | None = Query(None),
):
if any([label_names, label_ids, editor_login, title, content]):
return await issue_service.search_issues(label_names, label_ids, editor_login, title, content)
return await issue_service.get_all()


@router.get(path="/{id}", response_model=IssueResponseTo, status_code=status.HTTP_200_OK)
async def get_issue(id: int, issue_service: IssueServiceDep):
return await issue_service.get_by_id(id)


@router.put(path="/{id}", response_model=IssueResponseTo, status_code=status.HTTP_200_OK)
async def update_issue(id: int, issue_in: IssueRequestTo, issue_service: IssueServiceDep):
return await issue_service.update(id, issue_in)


@router.delete(path="/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_issue(id: int, issue_service: IssueServiceDep):
await issue_service.delete(id)


@router.get(path="/{id}/editor", response_model=EditorResponseTo, status_code=status.HTTP_200_OK)
async def get_editor_by_issue(id: int, issue_service: IssueServiceDep):
return await issue_service.get_editor_by_issue(id)


@router.get(path="/{id}/labels", response_model=list[LabelResponseTo], status_code=status.HTTP_200_OK)
async def get_labels_by_issue(id: int, issue_service: IssueServiceDep):
return await issue_service.get_labels_by_issue(id)


@router.get(path="/{id}/posts", response_model=list[PostResponseTo], status_code=status.HTTP_200_OK)
async def get_posts_by_issue(id: int, issue_service: IssueServiceDep):
return await issue_service.get_posts_by_issue(id)
31 changes: 31 additions & 0 deletions 351001/Budnikov/src/api/v1/endpoints/labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from fastapi import APIRouter, status
from src.schemas.dto import LabelRequestTo, LabelResponseTo
from src.dependencies.services import LabelServiceDep


router = APIRouter(prefix="/labels")


@router.post(path="", response_model=LabelResponseTo, status_code=status.HTTP_201_CREATED)
async def create_label(label_in: LabelRequestTo, label_service: LabelServiceDep):
return await label_service.create(label_in)


@router.get(path="", response_model=list[LabelResponseTo], status_code=status.HTTP_200_OK)
async def get_labels(label_service: LabelServiceDep):
return await label_service.get_all()


@router.get(path="/{id}", response_model=LabelResponseTo, status_code=status.HTTP_200_OK)
async def get_label(id: int, label_service: LabelServiceDep):
return await label_service.get_by_id(id)


@router.put(path="/{id}", response_model=LabelResponseTo, status_code=status.HTTP_200_OK)
async def update_label(id: int, label_in: LabelRequestTo, label_service: LabelServiceDep):
return await label_service.update(id, label_in)


@router.delete(path="/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_label(id: int, label_service: LabelServiceDep):
await label_service.delete(id)
31 changes: 31 additions & 0 deletions 351001/Budnikov/src/api/v1/endpoints/posts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from fastapi import APIRouter, status
from src.schemas.dto import PostRequestTo, PostResponseTo
from src.dependencies.services import PostServiceDep


router = APIRouter(prefix="/posts")


@router.post(path="", response_model=PostResponseTo, status_code=status.HTTP_201_CREATED)
async def create_post(post_in: PostRequestTo, post_service: PostServiceDep):
return await post_service.create(post_in)


@router.get(path="", response_model=list[PostResponseTo], status_code=status.HTTP_200_OK)
async def get_posts(post_service: PostServiceDep):
return await post_service.get_all()


@router.get(path="/{id}", response_model=PostResponseTo, status_code=status.HTTP_200_OK)
async def get_post(id: int, post_service: PostServiceDep):
return await post_service.get_by_id(id)


@router.put(path="/{id}", response_model=PostResponseTo, status_code=status.HTTP_200_OK)
async def update_post(id: int, post_in: PostRequestTo, post_service: PostServiceDep):
return await post_service.update(id, post_in)


@router.delete(path="/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_post(id: int, post_service: PostServiceDep):
await post_service.delete(id)
23 changes: 23 additions & 0 deletions 351001/Budnikov/src/api/v1/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from fastapi import APIRouter
from fastapi.responses import JSONResponse

from src.api.v1.endpoints.editors import router as editor_router
from src.api.v1.endpoints.labels import router as label_router
from src.api.v1.endpoints.posts import router as post_router
from src.api.v1.endpoints.issues import router as issue_router


api_router = APIRouter(prefix="/v1.0")

api_router.include_router(editor_router, tags=["Editors"])
api_router.include_router(label_router, tags=["Labels"])
api_router.include_router(post_router, tags=["Posts"])
api_router.include_router(issue_router, tags=["Issues"])


@api_router.get("/healthcheck")
async def healthcheck():
return JSONResponse(
status_code=200,
content={"server": "ok"},
)
1 change: 1 addition & 0 deletions 351001/Budnikov/src/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .database import TORTOISE_CONFIG as TORTOISE_CONFIG
Empty file.
19 changes: 19 additions & 0 deletions 351001/Budnikov/src/config/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
TORTOISE_CONFIG = {
"connections": {
"default": {
"engine": "tortoise.backends.sqlite",
"credentials": {"file_path": "db.sqlite3"},
}
},
"apps": {
"models": {
"models": [
"src.models.editor",
"src.models.post",
"src.models.issue",
"src.models.label",
],
"default_connection": "default",
}
},
}
Empty file.
5 changes: 5 additions & 0 deletions 351001/Budnikov/src/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class BaseAppException(Exception):
def __init__(self, status_code: int, error_code: str, error_message: str):
self.status_code = status_code
self.error_code = error_code
self.error_message = error_message
Empty file.
26 changes: 26 additions & 0 deletions 351001/Budnikov/src/dependencies/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Annotated
from fastapi import Depends

from src.services import EditorService, LabelService, PostService, IssueService


def get_editor_service():
return EditorService()


def get_label_service():
return LabelService()


def get_post_service():
return PostService()


def get_issue_service():
return IssueService()


type EditorServiceDep = Annotated[EditorService, Depends(get_editor_service)]
type LabelServiceDep = Annotated[LabelService, Depends(get_label_service)]
type PostServiceDep = Annotated[PostService, Depends(get_post_service)]
type IssueServiceDep = Annotated[IssueService, Depends(get_issue_service)]
61 changes: 61 additions & 0 deletions 351001/Budnikov/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import sys
import logging
from contextlib import asynccontextmanager

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from tortoise.contrib.fastapi import register_tortoise

from src.api.v1.router import api_router
from src.core.exceptions import BaseAppException
from src.config import TORTOISE_CONFIG


@asynccontextmanager
async def lifespan(app: FastAPI):
# await AsyncORM.create_tables() # Uncomment to use instead of migrations

yield

# actions after


def create_fastapi_app():
app = FastAPI(lifespan=lifespan, redirect_slashes=False)

app.include_router(api_router, prefix="/api")

register_tortoise(
app,
config=TORTOISE_CONFIG,
generate_schemas=True,
add_exception_handlers=True,
)

return app


def init_logger():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.FileHandler("app.log"), logging.StreamHandler(sys.stdout)],
)
logger = logging.getLogger(__name__)
logger.info("Logger is initialized")


init_logger()

app = create_fastapi_app()


@app.exception_handler(BaseAppException)
async def app_exception_handler(request: Request, exc: BaseAppException):
return JSONResponse(
status_code=exc.status_code,
content={
"errorCode": exc.error_code,
"errorMessage": exc.error_message,
},
)
4 changes: 4 additions & 0 deletions 351001/Budnikov/src/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .editor import Editor as Editor
from .label import Label as Label
from .issue import Issue as Issue
from .post import Post as Post
14 changes: 14 additions & 0 deletions 351001/Budnikov/src/models/editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from tortoise import fields, models


class Editor(models.Model):
id = fields.BigIntField(pk=True)
login = fields.CharField(max_length=64, unique=True)
password = fields.CharField(max_length=128)
firstname = fields.CharField(max_length=64)
lastname = fields.CharField(max_length=64)

issues: fields.ReverseRelation["Issue"]

class Meta:
table = "editors"
16 changes: 16 additions & 0 deletions 351001/Budnikov/src/models/issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from tortoise import fields, models


class Issue(models.Model):
id = fields.BigIntField(pk=True)
editor = fields.ForeignKeyField("models.Editor", related_name="issues")
title = fields.CharField(max_length=64)
content = fields.CharField(max_length=2048)
created = fields.DatetimeField(auto_now_add=True)
modified = fields.DatetimeField(auto_now=True)
labels = fields.ManyToManyField("models.Label", related_name="issues", through="m2m_issues_labels")

posts: fields.ReverseRelation["Post"]

class Meta:
table = "issues"
11 changes: 11 additions & 0 deletions 351001/Budnikov/src/models/label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from tortoise import fields, models


class Label(models.Model):
id = fields.BigIntField(pk=True)
name = fields.CharField(max_length=32, unique=True)

issues: fields.ManyToManyRelation["Issue"]

class Meta:
table = "labels"
7 changes: 7 additions & 0 deletions 351001/Budnikov/src/models/post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from tortoise import fields, models


class Post(models.Model):
id = fields.IntField(pk=True)
content = fields.TextField()
issue = fields.ForeignKeyField("models.Issue", related_name="posts")
Empty file.
Loading