Skip to content
Merged
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
6 changes: 4 additions & 2 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ jobs:
--network=web \
--env DB_DSN='${{ secrets.DB_DSN }}' \
--env ROOT_PATH='/services' \
--env GUNICORN_CMD_ARGS='--log-config logging_test.conf' \
--env AUTH_URL='https://api.test.profcomff.com/auth' \
--env GUNICORN_CMD_ARGS='--log-config logging_test.conf' \
--name ${{ env.CONTAITER_NAME }} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test

Expand Down Expand Up @@ -129,6 +130,7 @@ jobs:
--network=web \
--env DB_DSN='${{ secrets.DB_DSN }}' \
--env ROOT_PATH='/services' \
--env GUNICORN_CMD_ARGS='--log-config logging_prod.conf' \
--env AUTH_URL='https://api.profcomff.com/auth' \
--env GUNICORN_CMD_ARGS='--log-config logging_prod.conf' \
--name ${{ env.CONTAITER_NAME }} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
run:
source ./venv/bin/activate && uvicorn --reload --log-level debug services_backend.routes.base:app
source ./venv/bin/activate && uvicorn --reload --log-config logging_dev.conf services_backend.routes.base:app

configure: venv
source ./venv/bin/activate && pip install -r requirements.dev.txt -r requirements.txt

venv:
python3.11 -m venv venv

format:
autoflake -r --in-place --remove-all-unused-imports ./services_backend
isort ./services_backend
black ./services_backend

db:
docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-services-backend postgres:15
Expand Down
21 changes: 21 additions & 0 deletions logging_dev.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[loggers]
keys=root

[handlers]
keys=all

[formatters]
keys=main

[logger_root]
level=DEBUG
handlers=all

[handler_all]
class=StreamHandler
formatter=main
level=DEBUG
args=(sys.stdout,)

[formatter_main]
format=%(asctime)s %(levelname)-8s %(name)-15s %(message)s
1 change: 1 addition & 0 deletions logging_prod.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ keys=json
[logger_root]
level=INFO
handlers=all
formatter=json

[logger_gunicorn.error]
level=INFO
Expand Down
6 changes: 5 additions & 1 deletion requirements.dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pytest
pytest-cov
httpx
httpx
pytest_mock
autoflake
isort
black
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ alembic
SQLAlchemy
gunicorn
logging-profcomff
auth-lib-profcomff[fastapi]
3 changes: 2 additions & 1 deletion services_backend/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .routes.base import app
import uvicorn

from .routes.base import app


if __name__ == '__main__':
uvicorn.run(app)
1 change: 1 addition & 0 deletions services_backend/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .database import Button, Category


__all__ = ["Button", "Category"]
1 change: 1 addition & 0 deletions services_backend/models/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re

from sqlalchemy.ext.declarative import as_declarative, declared_attr


Expand Down
6 changes: 4 additions & 2 deletions services_backend/models/database.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column

from sqlalchemy import ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship

from .base import Base


Expand Down
1 change: 0 additions & 1 deletion services_backend/routes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
title='API управления списком сервисов',
description='Программный интерфейс управления списком сервисов в приложении Твой ФФ!',
version=__version__,

# Настраиваем интернет документацию
root_path=settings.ROOT_PATH if __version__ != 'dev' else '/',
docs_url=None if __version__ != 'dev' else '/docs',
Expand Down
66 changes: 58 additions & 8 deletions services_backend/routes/button.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
from fastapi import HTTPException, APIRouter
import logging

from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends, HTTPException
from fastapi_sqlalchemy import db

from .models.button import ButtonCreate, ButtonUpdate, ButtonGet
from .models.category import CategoryGet
from ..models.database import Button, Category
from .models.button import ButtonCreate, ButtonGet, ButtonUpdate
from .models.category import CategoryGet


logger = logging.getLogger(__name__)
button = APIRouter()


@button.post("/", response_model=ButtonGet)
def create_button(button_inp: ButtonCreate, category_id: int):
def create_button(
button_inp: ButtonCreate,
category_id: int,
user=Depends(UnionAuth(['services.button.create'])),
):
"""Создать кнопку

Необходимые scopes: `services.button.create`
"""
logger.info(f"User {user.get('id')} triggered create_button")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
if not category:
raise HTTPException(status_code=404, detail="Category does not exist")
Expand All @@ -24,15 +38,32 @@ def create_button(button_inp: ButtonCreate, category_id: int):


@button.get("/", response_model=CategoryGet)
def get_buttons(category_id: int):
def get_buttons(
category_id: int,
user=Depends(UnionAuth(allow_none=True, auto_error=False)),
):
"""Показать все кнопки в категории

Необходимые scopes: `-`
"""
logger.info(f"User {user.get('id')} triggered create_category")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
if not category:
raise HTTPException(status_code=404, detail="Category does not exist")
return category


@button.get("/{button_id}", response_model=ButtonGet)
def get_button(button_id: int, category_id: int):
def get_button(
button_id: int,
category_id: int,
user=Depends(UnionAuth(allow_none=True, auto_error=False)),
):
"""Показать одну кнопку

Необходимые scopes: `-`
"""
logger.info(f"User {user.get('id')} triggered create_category")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
if not category:
raise HTTPException(status_code=404, detail="Category does not exist")
Expand All @@ -45,7 +76,16 @@ def get_button(button_id: int, category_id: int):


@button.delete("/{button_id}", response_model=None)
def remove_button(button_id: int, category_id: int):
def remove_button(
button_id: int,
category_id: int,
user=Depends(UnionAuth(['services.button.remove'])),
):
"""Удалить кнопку

Необходимые scopes: `services.button.remove`
"""
logger.info(f"User {user.get('id')} triggered create_category")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
if not category:
raise HTTPException(status_code=404, detail="Category does not exist")
Expand All @@ -60,7 +100,17 @@ def remove_button(button_id: int, category_id: int):


@button.patch("/{button_id}", response_model=ButtonUpdate)
def update_button(button_inp: ButtonUpdate, button_id: int, category_id: int):
def update_button(
button_inp: ButtonUpdate,
button_id: int,
category_id: int,
user=Depends(UnionAuth(['services.button.update'])),
):
"""Обновить кнопку

Необходимые scopes: `services.button.update`
"""
logger.info(f"User {user.get('id')} triggered create_category")
query = db.session.query(Button).filter(Button.id == button_id)
button = query.one_or_none()
last_button = (
Expand Down
69 changes: 57 additions & 12 deletions services_backend/routes/category.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
from fastapi import HTTPException, APIRouter
import logging
from typing import Literal

from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi_sqlalchemy import db

from .models.category import CategoryCreate, CategoryUpdate, CategoryGet
from ..models.database import Category, Button
from ..models.database import Button, Category
from .models.category import CategoryCreate, CategoryGet, CategoryUpdate


logger = logging.getLogger(__name__)
category = APIRouter()


@category.post("/", response_model=CategoryGet)
def create_category(category_inp: CategoryCreate):
def create_category(
category_inp: CategoryCreate,
user=Depends(UnionAuth(['services.category.create'])),
):
"""Создает категорию

Необходимые scopes: `services.category.create`
"""
logger.info(f"User {user.get('id')} triggered create_category")
last_category = db.session.query(Category).order_by(Category.order.desc()).first()
category = Category(**category_inp.dict(exclude_none=True))
if last_category:
Expand All @@ -19,17 +33,31 @@ def create_category(category_inp: CategoryCreate):


@category.get("/", response_model=list[CategoryGet], response_model_exclude_none=True)
def get_categories(offset: int = 0, limit: int = 100):
if (offset < 0) or (limit < 0):
raise HTTPException(400, detail="Offset or limit cant be negative")
def get_categories(
info: list[Literal['buttons']] = Query([]),
user=Depends(UnionAuth(allow_none=True, auto_error=False)),
):
"""Показывает список категорий

Необходимые scopes: `-`
"""
logger.info(f"User {user.get('id')} triggered get_categories")
return [
CategoryGet.from_orm(row).dict(exclude={"buttons"})
for row in db.session.query(Category).order_by(Category.order).offset(offset).limit(limit).all()
CategoryGet.from_orm(row).dict(exclude={"buttons"} if 'buttons' not in info else {})
for row in db.session.query(Category).order_by(Category.order).all()
]


@category.get("/{category_id}", response_model=CategoryGet, response_model_exclude_none=True)
def get_category(category_id: int):
def get_category(
category_id: int,
user=Depends(UnionAuth(allow_none=True, auto_error=False)),
):
"""Показывает категорию

Необходимые scopes: `-`
"""
logger.info(f"User {user.get('id')} triggered get_category")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
if not category:
raise HTTPException(status_code=404, detail="Category does not exist")
Expand All @@ -42,7 +70,15 @@ def get_category(category_id: int):


@category.delete("/{category_id}", response_model=None)
def remove_category(category_id: int):
def remove_category(
category_id: int,
user=Depends(UnionAuth(['services.category.delete'])),
):
"""Удаляет категорию и все кнопки в ней

Необходимые scopes: `services.category.delete`
"""
logger.info(f"User {user.get('id')} triggered remove_category")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
if not category:
raise HTTPException(status_code=404, detail="Category does not exist")
Expand All @@ -55,7 +91,16 @@ def remove_category(category_id: int):


@category.patch("/{category_id}", response_model=CategoryUpdate)
def update_category(category_inp: CategoryUpdate, category_id: int):
def update_category(
category_inp: CategoryUpdate,
category_id: int,
user=Depends(UnionAuth(['services.category.update'])),
):
"""Обновляет категорию

Необходимые scopes: `services.category.update`
"""
logger.info(f"User {user.get('id')} triggered update_category")
category = db.session.query(Category).filter(Category.id == category_id).one_or_none()
last_category = db.session.query(Category).order_by(Category.order.desc()).first()

Expand Down
2 changes: 1 addition & 1 deletion services_backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class Settings(BaseSettings):
"""Application settings"""

DB_DSN: PostgresDsn
DB_DSN: PostgresDsn = 'postgresql://postgres@localhost:5432/postgres'
ROOT_PATH: str = '/' + os.getenv('APP_NAME', '')

CORS_ALLOW_ORIGINS: list[str] = ['*']
Expand Down
19 changes: 15 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session, sessionmaker
from pytest_mock import MockerFixture
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from services_backend.models.base import Base
from services_backend.routes.base import app
from services_backend.settings import get_settings
from services_backend.models.base import Base


@pytest.fixture(scope='session')
def client():
@pytest.fixture()
def client(mocker: MockerFixture):
user_mock = mocker.patch('auth_lib.fastapi.UnionAuth.__call__')
user_mock.return_value = {
"session_scopes": [{"id": 0, "name": "string", "comment": "string"}],
"user_scopes": [{"id": 0, "name": "string", "comment": "string"}],
"indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}],
"groups": [{"id": 0, "name": "string", "parent_id": 0}],
"id": 0,
"email": "string",
}
client = TestClient(app)
return client

Expand Down