2 Commits

Author SHA1 Message Date
TheNoxium
3933a05582 feat: endpoints services 2025-11-06 23:34:47 +05:00
453efdd095 Merge pull request 'feat: delete ps node' (#20) from VORKOUT-29 into master
Reviewed-on: #20
Reviewed-by: cyrussmeat <dr.cyrill@gmail.com>
Reviewed-by: Vladislav Syrochkin <vlad.dev@heado.ru>
2025-11-05 14:28:51 +05:00
17 changed files with 727 additions and 359 deletions

View File

@@ -37,7 +37,7 @@ async def update_key_by_id(connection: AsyncConnection, update_values, key) -> O
await connection.commit() await connection.commit()
async def create_key(connection: AsyncConnection, key: AccountKeyring, key_id: int) -> Optional[AccountKeyring]: async def create_key(connection: AsyncConnection, key: AccountKeyring, key_id: str) -> Optional[AccountKeyring]:
""" """
Создает нове поле в таблице account_keyring_table). Создает нове поле в таблице account_keyring_table).
""" """

View File

@@ -1,23 +1,17 @@
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, Query, status
from orm.tables.account import AccountStatus
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.db.logic.account import ( from api.db.logic.account import get_user_by_login
create_user,
get_user_account_page_DTO,
get_user_by_id,
get_user_by_login,
update_user_by_id,
)
from api.db.logic.keyring import create_password_key, update_password_key
from api.schemas.account.account import User from api.schemas.account.account import User
from api.schemas.base import bearer_schema from api.schemas.base import bearer_schema
from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserFilterDTO, UserUpdate from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserUpdate, UserFilterDTO
from api.services.auth import get_current_user from api.services.auth import get_current_user
from api.services.user_role_validation import UserRoleValidator from api.services.user_role_validation import UserRoleValidator
from api.error import create_operation_error, create_validation_error
from api.services.endpoints.account import AccountService
api_router = APIRouter( api_router = APIRouter(
prefix="/account", prefix="/account",
@@ -39,7 +33,7 @@ async def get_all_account_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
filters = { filters = {
**({"status": status_filter} if status_filter else {}), **({"status": status_filter} if status_filter else {}),
@@ -54,10 +48,14 @@ async def get_all_account_endpoint(
filters=filters if filters else None, filters=filters if filters else None,
) )
user_list = await get_user_account_page_DTO(connection, filter_dto) service = AccountService(connection)
user_list = await service.list(filter_dto)
if user_list is None: if user_list is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Accounts not found") raise create_operation_error(
message="Accounts not found",
status_code=status.HTTP_404_NOT_FOUND,
)
return user_list return user_list
@@ -69,12 +67,17 @@ async def get_account_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
user = await get_user_by_id(connection, user_id) service = AccountService(connection)
user = await service.get(user_id)
if user is None: if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") raise create_operation_error(
message="Account not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"user_id": user_id},
)
return user return user
@@ -91,12 +94,14 @@ async def create_account_endpoint(
user_validation = await get_user_by_login(connection, user.login) user_validation = await get_user_by_login(connection, user.login)
if user_validation is None: if user_validation is None:
new_user = await create_user(connection, user, authorize_user.id) service = AccountService(connection)
await create_password_key(connection, user.password, new_user.id) new_user = await service.create(user_data=user, creator_id=authorize_user.id)
return new_user return new_user
else: else:
raise HTTPException( raise create_validation_error(
status_code=status.HTTP_400_BAD_REQUEST, detail="An account with this information already exists." message="An account with this information already exists.",
status_code=status.HTTP_400_BAD_REQUEST,
details={"login": user.login},
) )
@@ -108,25 +113,30 @@ async def update_account_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
service = AccountService(connection)
user = await service.get(user_id)
user = await get_user_by_id(connection, user_id)
if user is None: if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") raise create_operation_error(
message="Account not found",
if user_update.password is not None: status_code=status.HTTP_404_NOT_FOUND,
await update_password_key(connection, user.id, user_update.password) details={"user_id": user_id},
)
updated_values = user_update.model_dump(by_alias=True, exclude_none=True) updated_values = user_update.model_dump(by_alias=True, exclude_none=True)
if not updated_values: if not updated_values:
return user return user
await update_user_by_id(connection, updated_values, user) updated_user = await service.update(
user_id=user_id,
user_update_data=updated_values,
password=user_update.password,
)
user = await get_user_by_id(connection, user_id) return updated_user
return user
@api_router.delete("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User) @api_router.delete("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
@@ -136,21 +146,17 @@ async def delete_account_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
service = AccountService(connection)
user = await service.get(user_id)
user = await get_user_by_id(connection, user_id)
if user is None: if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") raise create_operation_error(
message="Account not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"user_id": user_id},
)
deleted_user = await service.delete(user_id=user_id)
user_update = UserUpdate(status=AccountStatus.DELETED.value) return deleted_user
updated_values = user_update.model_dump(by_alias=True, exclude_none=True)
if not updated_values:
return user
await update_user_by_id(connection, updated_values, user)
user = await get_user_by_id(connection, user_id)
return user

View File

@@ -1,15 +1,11 @@
from datetime import datetime, timedelta, timezone
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
Depends, Depends,
HTTPException,
Response, Response,
status, status,
Request, Request,
) )
from loguru import logger
from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth import AuthJWT
from pydantic import BaseModel from pydantic import BaseModel
@@ -19,10 +15,10 @@ from sqlalchemy.ext.asyncio import AsyncConnection
from api.config import get_settings from api.config import get_settings
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.services.auth import authenticate_user from api.services.auth import authenticate_user
from api.services.endpoints.auth import AuthService
from api.db.logic.auth import add_new_refresh_token, upgrade_old_refresh_token
from api.schemas.endpoints.auth import Auth, Tokens from api.schemas.endpoints.auth import Auth, Tokens
from api.error import create_access_error
api_router = APIRouter( api_router = APIRouter(
prefix="/auth", prefix="/auth",
@@ -57,30 +53,18 @@ async def login_for_access_token_endpoint(
): ):
"""Авторизирует, выставляет токены в куки.""" """Авторизирует, выставляет токены в куки."""
user = await authenticate_user(connection, user.login, user.password) authenticated_user = await authenticate_user(connection, user.login, user.password)
# print("login_for_access_token", user) if not authenticated_user:
raise create_access_error(
if not user: message="Incorrect username or password",
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
# headers={"WWW-Authenticate": "Bearer"},
) )
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES) service = AuthService(connection)
refresh_token_expires = timedelta(days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS) tokens = await service.login(authenticated_user, Authorize)
logger.debug(f"refresh_token_expires {refresh_token_expires}") return tokens
access_token = Authorize.create_access_token(subject=user.login, expires_time=access_token_expires)
refresh_token = Authorize.create_refresh_token(subject=user.login, expires_time=refresh_token_expires)
refresh_token_expires_time = datetime.now(timezone.utc) + refresh_token_expires
await add_new_refresh_token(connection, refresh_token, refresh_token_expires_time, user)
return Tokens(access_token=access_token, refresh_token=refresh_token)
@api_router.post("/refresh", response_model=Tokens) @api_router.post("/refresh", response_model=Tokens)
@@ -89,18 +73,19 @@ async def refresh_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
Authorize: AuthJWT = Depends(), Authorize: AuthJWT = Depends(),
) -> Tokens: ) -> Tokens:
service = AuthService(connection)
try: try:
Authorize.jwt_refresh_token_required() Authorize.jwt_refresh_token_required()
current_user = Authorize.get_jwt_subject() current_user = Authorize.get_jwt_subject()
except Exception: except Exception:
refresh_token = request.headers.get("Authorization").split(" ")[1] refresh_token = request.headers.get("Authorization").split(" ")[1]
await upgrade_old_refresh_token(connection, refresh_token) await service.invalidate_refresh_token(refresh_token)
raise HTTPException( raise create_access_error(
message="Invalid refresh token",
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token",
) )
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES) tokens = await service.refresh(current_user, Authorize)
new_access_token = Authorize.create_access_token(subject=current_user, expires_time=access_token_expires)
return Tokens(access_token=new_access_token) return tokens

View File

@@ -1,19 +1,18 @@
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
Depends, Depends,
HTTPException,
status, status,
) )
from orm.tables.account import KeyStatus
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.db.logic.keyring import create_key, get_key_by_id, update_key_by_id
from api.schemas.account.account_keyring import AccountKeyring from api.schemas.account.account_keyring import AccountKeyring
from api.schemas.base import bearer_schema from api.schemas.base import bearer_schema
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.services.auth import get_current_user from api.services.auth import get_current_user
from api.services.user_role_validation import UserRoleValidator from api.services.user_role_validation import UserRoleValidator
from api.error import create_operation_error, create_validation_error
from api.services.endpoints.keyring import KeyringService
api_router = APIRouter( api_router = APIRouter(
prefix="/keyring", prefix="/keyring",
@@ -26,12 +25,17 @@ async def get_keyring_endpoint(
key_id: str, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user) key_id: str, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
keyring = await get_key_by_id(connection, key_id) service = KeyringService(connection)
keyring = await service.get(key_id)
if keyring is None: if keyring is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Key not found") raise create_operation_error(
message="Key not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"key_id": key_id},
)
return keyring return keyring
@@ -45,21 +49,20 @@ async def create_keyring_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
keyring = await get_key_by_id(connection, key_id) service = KeyringService(connection)
keyring = await service.get(key_id)
if keyring is None: if keyring is None:
keyring_new = await create_key( keyring_new = await service.create(key, key_id)
connection,
key,
key_id,
)
return keyring_new return keyring_new
else: else:
raise HTTPException( raise create_validation_error(
status_code=status.HTTP_400_BAD_REQUEST, detail="An keyring with this information already exists." message="A keyring with this information already exists.",
status_code=status.HTTP_400_BAD_REQUEST,
details={"key_id": key_id},
) )
@@ -72,22 +75,26 @@ async def update_keyring_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
service = KeyringService(connection)
keyring = await service.get(key_id)
keyring = await get_key_by_id(connection, key_id)
if keyring is None: if keyring is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="keyring not found") raise create_operation_error(
message="keyring not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"key_id": key_id},
)
updated_values = keyring_update.model_dump(by_alias=True, exclude_none=True) updated_values = keyring_update.model_dump(by_alias=True, exclude_none=True)
if not updated_values: if not updated_values:
return keyring return keyring
await update_key_by_id(connection, updated_values, keyring) updated_keyring = await service.update(key_id, updated_values)
keyring = await get_key_by_id(connection, key_id) return updated_keyring
return keyring
@api_router.delete("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring) @api_router.delete("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
@@ -98,21 +105,18 @@ async def delete_keyring_endpoint(
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_admin(current_user) await validator.validate_admin(current_user)
service = KeyringService(connection)
keyring = await service.get(key_id)
keyring = await get_key_by_id(connection, key_id)
if keyring is None: if keyring is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="keyring not found") raise create_operation_error(
message="keyring not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"key_id": key_id},
)
keyring_update = AccountKeyringUpdate(status=KeyStatus.DELETED.value) deleted_keyring = await service.delete(key_id)
updated_values = keyring_update.model_dump(by_alias=True, exclude_none=True) return deleted_keyring
if not updated_values:
return keyring
await update_key_by_id(connection, updated_values, keyring)
keyring = await get_key_by_id(connection, key_id)
return keyring

View File

@@ -1,23 +1,17 @@
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, Query, status
from orm.tables.events import EventStatus
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.db.logic.account import get_user_by_login from api.db.logic.account import get_user_by_login
from api.db.logic.list_events import (
create_list_events,
get_list_events_by_id,
get_list_events_by_name,
get_list_events_page_DTO,
update_list_events_by_id,
)
from api.schemas.base import bearer_schema from api.schemas.base import bearer_schema
from api.schemas.endpoints.list_events import AllListEventResponse, ListEventFilterDTO, ListEventUpdate from api.schemas.endpoints.list_events import AllListEventResponse, ListEventFilterDTO, ListEventUpdate
from api.schemas.events.list_events import ListEvent from api.schemas.events.list_events import ListEvent
from api.services.auth import get_current_user from api.services.auth import get_current_user
from api.services.user_role_validation import UserRoleValidator from api.services.user_role_validation import UserRoleValidator
from api.error import create_operation_error, create_validation_error
from api.services.endpoints.list_events import ListEventsService
api_router = APIRouter( api_router = APIRouter(
prefix="/list_events", prefix="/list_events",
@@ -59,10 +53,14 @@ async def get_all_list_events_endpoint(
filter_dto.filters = {} filter_dto.filters = {}
filter_dto.filters["creator_id"] = [str(authorize_user.id)] filter_dto.filters["creator_id"] = [str(authorize_user.id)]
list_events_page = await get_list_events_page_DTO(connection, filter_dto) service = ListEventsService(connection)
list_events_page = await service.list(filter_dto)
if list_events_page is None: if list_events_page is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found") raise create_operation_error(
message="List events not found",
status_code=status.HTTP_404_NOT_FOUND,
)
return list_events_page return list_events_page
@@ -73,16 +71,18 @@ async def get_list_events_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
list_events_validation = await get_list_events_by_id(connection, list_events_id) service = ListEventsService(connection)
list_events_validation = await service.get(list_events_id)
if list_events_validation is None: if list_events_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found") raise create_operation_error(
message="List events not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"list_events_id": list_events_id},
)
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_ownership(current_user, list_events_validation.creator_id) await validator.validate_ownership(current_user, list_events_validation.creator_id)
if list_events_id is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
return list_events_validation return list_events_validation
@@ -93,19 +93,34 @@ async def create_list_events_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
user_validation = await get_user_by_login(connection, current_user) if list_events.name is None:
list_events_validation = await get_list_events_by_name(connection, list_events.name) raise create_validation_error(
message="Name is required for list event creation",
if list_events_validation is None: status_code=status.HTTP_400_BAD_REQUEST,
await create_list_events(connection, list_events, user_validation.id)
list_events_new = await get_list_events_by_name(connection, list_events.name)
return list_events_new
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="An List events with this information already exists."
) )
user_validation = await get_user_by_login(connection, current_user)
service = ListEventsService(connection)
list_events_validation = await service.get_by_name(list_events.name)
if list_events_validation is not None:
raise create_validation_error(
message="A List events with this information already exists.",
status_code=status.HTTP_400_BAD_REQUEST,
details={"name": list_events.name},
)
list_events_new = await service.create(list_events, user_validation.id)
if list_events_new is None:
raise create_operation_error(
message="Failed to create list event",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return list_events_new
@api_router.put("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent) @api_router.put("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
async def update_list_events( async def update_list_events(
@@ -114,24 +129,27 @@ async def update_list_events(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
list_events_validation = await get_list_events_by_id(connection, list_events_id) service = ListEventsService(connection)
list_events_validation = await service.get(list_events_id)
if list_events_validation is None: if list_events_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found") raise create_operation_error(
message="List events not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"list_events_id": list_events_id},
)
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_ownership(current_user, list_events_validation.creator_id) await validator.validate_ownership(current_user, list_events_validation.creator_id)
updated_values = list_events_update.model_dump(by_alias=True, exclude_none=True) updated_values = list_events_update.model_dump(by_alias=True, exclude_none=True)
if not updated_values: if not updated_values:
return list_events_validation return list_events_validation
await update_list_events_by_id(connection, updated_values, list_events_validation) updated_list_events = await service.update(list_events_id, updated_values)
list_events = await get_list_events_by_id(connection, list_events_id) return updated_list_events
return list_events
@api_router.delete("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent) @api_router.delete("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
@@ -140,23 +158,19 @@ async def delete_list_events_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
list_events_validation = await get_list_events_by_id(connection, list_events_id) service = ListEventsService(connection)
list_events_validation = await service.get(list_events_id)
if list_events_validation is None: if list_events_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found") raise create_operation_error(
message="List events not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"list_events_id": list_events_id},
)
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_ownership(current_user, list_events_validation.creator_id) await validator.validate_ownership(current_user, list_events_validation.creator_id)
list_events_update = ListEventUpdate(status=EventStatus.DELETED.value) deleted_list_events = await service.delete(list_events_id)
updated_values = list_events_update.model_dump(by_alias=True, exclude_none=True) return deleted_list_events
if not updated_values:
return list_events_validation
await update_list_events_by_id(connection, updated_values, list_events_validation)
list_events = await get_list_events_by_id(connection, list_events_id)
return list_events

View File

@@ -1,38 +1,18 @@
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, Query, status, HTTPException
from orm.tables.process import ProcessStatus
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.db.logic.account import get_user_by_login from api.db.logic.account import get_user_by_login
from api.db.logic.process_schema import (
create_process_schema,
get_process_schema_by_id,
get_process_schema_page_DTO,
update_process_schema_by_id,
)
from api.schemas.base import bearer_schema from api.schemas.base import bearer_schema
from api.schemas.endpoints.process_schema import AllProcessSchemaResponse, ProcessSchemaFilterDTO, ProcessSchemaUpdate from api.schemas.endpoints.process_schema import AllProcessSchemaResponse, ProcessSchemaFilterDTO, ProcessSchemaUpdate
from api.schemas.process.process_schema import ProcessSchema, ProcessSchemaSettingsNode, ProcessSchemaResponse from api.schemas.process.process_schema import ProcessSchema, ProcessSchemaResponse
from api.schemas.process.ps_node import Ps_NodeFrontResponseNode
from api.schemas.process.ps_node import Ps_NodeFrontResponse
from api.services.auth import get_current_user from api.services.auth import get_current_user
from api.services.user_role_validation import UserRoleValidator from api.services.user_role_validation import UserRoleValidator
from api.db.logic.ps_node import create_ps_node_schema
from api.db.logic.process_schema import update_process_schema_settings_by_id
from orm.tables.process import NodeType
from api.utils.to_camel_dict import to_camel_dict from api.utils.to_camel_dict import to_camel_dict
from api.error import create_operation_error
from core import VorkNodeRegistry from api.services.endpoints.process_schema import ProcessSchemaService
from model_nodes import ListenNodeData
from api.utils.node_counter import increment_node_counter
api_router = APIRouter( api_router = APIRouter(
@@ -83,10 +63,14 @@ async def get_all_process_schema_endpoint(
filter_dto.filters = {} filter_dto.filters = {}
filter_dto.filters["creator_id"] = [str(authorize_user.id)] filter_dto.filters["creator_id"] = [str(authorize_user.id)]
process_schema_page = await get_process_schema_page_DTO(connection, filter_dto) service = ProcessSchemaService(connection)
process_schema_page = await service.list(filter_dto)
if process_schema_page is None: if process_schema_page is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found") raise create_operation_error(
message="Process schema not found",
status_code=status.HTTP_404_NOT_FOUND,
)
return to_camel_dict(process_schema_page.model_dump()) return to_camel_dict(process_schema_page.model_dump())
@@ -97,16 +81,25 @@ async def get_process_schema_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
process_schema_validation = await get_process_schema_by_id(connection, process_schema_id) service = ProcessSchemaService(connection)
process_schema_validation = await service.get(process_schema_id)
if process_schema_validation is None: if process_schema_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found") raise create_operation_error(
message="Process schema not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"process_schema_id": process_schema_id},
)
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_ownership(current_user, process_schema_validation.creator_id) await validator.validate_ownership(current_user, process_schema_validation.creator_id)
if process_schema_id is None: if process_schema_id is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found") raise create_operation_error(
message="Process schema not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"process_schema_id": process_schema_id},
)
return to_camel_dict(process_schema_validation.model_dump()) return to_camel_dict(process_schema_validation.model_dump())
@@ -118,57 +111,8 @@ async def create_processschema_endpoint(
): ):
user_validation = await get_user_by_login(connection, current_user) user_validation = await get_user_by_login(connection, current_user)
current_node_counter = increment_node_counter() service = ProcessSchemaService(connection)
title = f"Новая схема {current_node_counter}" response_data = await service.create(user_validation.id)
description = "Default description"
node_id = await create_process_schema(connection, user_validation.id, title, description)
process_schema_new = await get_process_schema_by_id(connection, node_id)
start_node_data = ListenNodeData(ps_id=process_schema_new.id, node_type=NodeType.START.value, is_start="True")
start_node_links = {}
registery = VorkNodeRegistry()
vork_node = registery.get("LISTEN")
node_descriptor = vork_node.form()
start_node = vork_node(data=start_node_data.model_dump(), links=start_node_links)
validated_start_schema = start_node.validate()
db_start_schema = await create_ps_node_schema(connection, validated_start_schema, user_validation.id)
node = ProcessSchemaSettingsNode(
id=db_start_schema.id,
node_type=NodeType.LISTEN.value,
data=validated_start_schema.data.model_dump(),
from_node=None,
links=None,
)
settings_dict = {"node": node.model_dump(mode="json")}
await update_process_schema_settings_by_id(connection, process_schema_new.id, settings_dict)
process_schema_new = await get_process_schema_by_id(connection, node_id)
ps_node_front_response = Ps_NodeFrontResponse(
description=node_descriptor.model_dump(),
node=Ps_NodeFrontResponseNode(
id=db_start_schema.id, node_type=NodeType.LISTEN.value, data=validated_start_schema.data.model_dump()
),
link=None,
)
response_data = {
"process_schema": process_schema_new.model_dump(),
"node_listen": ps_node_front_response.model_dump(),
}
return to_camel_dict(response_data) return to_camel_dict(response_data)
@@ -180,22 +124,25 @@ async def update_process_schema_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
process_schema_validation = await get_process_schema_by_id(connection, process_schema_id) service = ProcessSchemaService(connection)
process_schema_validation = await service.get(process_schema_id)
if process_schema_validation is None: if process_schema_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found") raise create_operation_error(
message="Process schema not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"process_schema_id": process_schema_id},
)
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_ownership(current_user, process_schema_validation.creator_id) await validator.validate_ownership(current_user, process_schema_validation.creator_id)
updated_values = process_schema_update.model_dump(by_alias=True, exclude_none=True) updated_values = process_schema_update.model_dump(by_alias=True, exclude_none=True)
if not updated_values: if not updated_values:
return process_schema_validation return process_schema_validation
await update_process_schema_by_id(connection, updated_values, process_schema_validation) process_schema = await service.update(process_schema_id, updated_values, process_schema_validation)
process_schema = await get_process_schema_by_id(connection, process_schema_id)
return process_schema return process_schema
@@ -206,23 +153,19 @@ async def delete_process_schema_endpoint(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
process_schema_validation = await get_process_schema_by_id(connection, process_schema_id) service = ProcessSchemaService(connection)
process_schema_validation = await service.get(process_schema_id)
if process_schema_validation is None: if process_schema_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found") raise create_operation_error(
message="Process schema not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"process_schema_id": process_schema_id},
)
validator = UserRoleValidator(connection) validator = UserRoleValidator(connection)
authorize_user = await validator.validate_ownership(current_user, process_schema_validation.creator_id) await validator.validate_ownership(current_user, process_schema_validation.creator_id)
process_schema_update = ProcessSchemaUpdate(status=ProcessStatus.DELETED.value) await service.delete(process_schema_id, process_schema_validation)
updated_values = process_schema_update.model_dump(by_alias=True, exclude_none=True)
if not updated_values:
return process_schema_validation
await update_process_schema_by_id(connection, updated_values, process_schema_validation)
await get_process_schema_by_id(connection, process_schema_id)
return HTTPException(status_code=status.HTTP_200_OK, detail="Process schema deleted successfully") return HTTPException(status_code=status.HTTP_200_OK, detail="Process schema deleted successfully")

View File

@@ -1,17 +1,17 @@
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
Depends, Depends,
HTTPException,
status, status,
) )
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.db.logic.account import get_user_by_id, get_user_by_login, update_user_by_id
from api.schemas.account.account import User from api.schemas.account.account import User
from api.schemas.base import bearer_schema from api.schemas.base import bearer_schema
from api.schemas.endpoints.account import UserUpdate from api.schemas.endpoints.account import UserUpdate
from api.services.auth import get_current_user from api.services.auth import get_current_user
from api.error import create_operation_error, create_validation_error
from api.services.endpoints.profile import ProfileService
api_router = APIRouter( api_router = APIRouter(
prefix="/profile", prefix="/profile",
@@ -23,10 +23,15 @@ api_router = APIRouter(
async def get_profile( async def get_profile(
connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user) connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
): ):
user = await get_user_by_login(connection, current_user) service = ProfileService(connection)
user = await service.get_by_login(current_user)
if user is None: if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") raise create_operation_error(
message="Account not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"user_id": current_user},
)
return user return user
@@ -37,20 +42,25 @@ async def update_profile(
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user), current_user=Depends(get_current_user),
): ):
user = await get_user_by_login(connection, current_user) service = ProfileService(connection)
user = await service.get_by_login(current_user)
if user is None: if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") raise create_operation_error(
message="Account not found",
status_code=status.HTTP_404_NOT_FOUND,
details={"user_id": current_user},
)
if user_update.role is None and user_update.login is None: if user_update.role is None and user_update.login is None:
updated_values = user_update.model_dump(by_alias=True, exclude_none=True) updated_values = user_update.model_dump(by_alias=True, exclude_none=True)
if updated_values is None: user = await service.update(user.id, updated_values, user)
return user
await update_user_by_id(connection, updated_values, user)
user = await get_user_by_id(connection, user.id)
return user return user
else: else:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Bad body") raise create_validation_error(
message="Bad body",
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
details={"reason": "role and login fields cannot be updated"},
)

View File

@@ -4,31 +4,15 @@ from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.db.logic.account import get_user_by_login from api.db.logic.account import get_user_by_login
from api.schemas.base import bearer_schema from api.schemas.base import bearer_schema
from api.schemas.process.process_schema import ProcessSchemaSettingsNodeLink, ProcessSchemaSettingsNode from api.schemas.process.ps_node import Ps_NodeFrontResponse, Ps_NodeRequest, Ps_NodeDeleteRequest
from api.schemas.process.ps_node import Ps_NodeFrontResponseNode, Ps_NodeRequest, Ps_NodeDeleteRequest
from api.schemas.process.ps_node import Ps_NodeFrontResponse
from api.services.auth import get_current_user from api.services.auth import get_current_user
from api.db.logic.ps_node import get_ps_node_by_id, check_node_connection
from api.db.logic.process_schema import get_process_schema_by_id
from api.db.logic.ps_node import (
create_ps_node_schema,
get_ps_node_by_id,
check_node_connection,
get_nodes_for_deletion_ordered,
delete_ps_nodes_delete_handler,
)
from api.db.logic.node_link import get_last_link_name_by_node_id, create_node_link_schema
from api.db.logic.process_schema import update_process_schema_settings_by_id, get_process_schema_by_id
from api.services.user_role_validation import UserRoleValidator from api.services.user_role_validation import UserRoleValidator
from core import VorkNodeRegistry
from core import VorkNodeRegistry, VorkNodeLink
from model_nodes import VorkNodeLinkData
from api.utils.to_camel_dict import to_camel_dict
from api.error import create_operation_error, create_access_error, create_validation_error, create_server_error from api.error import create_operation_error, create_access_error, create_validation_error, create_server_error
from api.services.endpoints.ps_node import PsNodeService
api_router = APIRouter( api_router = APIRouter(
@@ -92,20 +76,17 @@ async def delete_ps_node_endpoint(
}, },
) )
ordered_node_ids = await get_nodes_for_deletion_ordered(connection, ps_node_delete_data.next_node_id) service = PsNodeService(connection)
try: try:
deleted_node_ids = await delete_ps_nodes_delete_handler(connection, ordered_node_ids) result = await service.delete(ps_node_delete_data.next_node_id)
except Exception as e: except Exception as e:
raise create_server_error( raise create_server_error(
message="Failed to delete nodes", message="Failed to delete nodes",
status_code=500, status_code=500,
details={"error": str(e), "ordered_node_ids": ordered_node_ids}, details={"error": str(e), "next_node_id": ps_node_delete_data.next_node_id},
) )
return { return result
"deleted_node_ids": deleted_node_ids,
}
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=Ps_NodeFrontResponse) @api_router.post("", dependencies=[Depends(bearer_schema)], response_model=Ps_NodeFrontResponse)
@@ -145,12 +126,9 @@ async def create_ps_node_endpoint(
details={"node_type": ps_node.data["node_type"]}, details={"node_type": ps_node.data["node_type"]},
) )
node_descriptor = vork_node.form()
try: try:
node_instance = vork_node(data=ps_node.data, links=ps_node.links) node_instance = vork_node(data=ps_node.data, links=ps_node.links)
node_instance_validated = node_instance.validate() node_instance_validated = node_instance.validate()
except Exception as e: except Exception as e:
raise create_validation_error( raise create_validation_error(
message="Node validation failed", message="Node validation failed",
@@ -158,7 +136,6 @@ async def create_ps_node_endpoint(
details={"error": str(e)}, details={"error": str(e)},
) )
# Проверка: parent_id принадлежит тому же ps_id
parent_id = node_instance_validated.parent_id parent_id = node_instance_validated.parent_id
target_ps_id = ps_node.data["ps_id"] target_ps_id = ps_node.data["ps_id"]
@@ -176,19 +153,15 @@ async def create_ps_node_endpoint(
details={"parent_id": parent_id, "expected_ps_id": target_ps_id, "actual_ps_id": parent_node.ps_id}, details={"parent_id": parent_id, "expected_ps_id": target_ps_id, "actual_ps_id": parent_node.ps_id},
) )
# Проверка: у родительской ноды есть указанный порт
parent_port_number = node_instance_validated.parent_port_number parent_port_number = node_instance_validated.parent_port_number
# Извлекаем номера портов из settings родительской ноды
parent_settings = parent_node.settings or {} parent_settings = parent_node.settings or {}
available_port_numbers = [] available_port_numbers = []
# Ищем все ключи, содержащие "port" в названии
for key, value in parent_settings.items(): for key, value in parent_settings.items():
if "port" in key.lower() and isinstance(value, int): if "port" in key.lower() and isinstance(value, int):
available_port_numbers.append(value) available_port_numbers.append(value)
# Проверяем, что указанный порт существует в settings
if parent_port_number not in available_port_numbers: if parent_port_number not in available_port_numbers:
raise create_validation_error( raise create_validation_error(
message="Parent port number is invalid", message="Parent port number is invalid",
@@ -196,43 +169,9 @@ async def create_ps_node_endpoint(
details={"parent_id": parent_id, "parent_settings": parent_settings}, details={"parent_id": parent_id, "parent_settings": parent_settings},
) )
service = PsNodeService(connection)
try: try:
db_ps_node = await create_ps_node_schema(connection, node_instance_validated, user_validation.id) ps_node_front_response = await service.create(ps_node.data, ps_node.links, user_validation.id)
link_name = await get_last_link_name_by_node_id(connection, db_ps_node.ps_id)
link_data = VorkNodeLinkData(
parent_port_number=node_instance_validated.parent_port_number,
to_id=db_ps_node.id,
from_id=node_instance_validated.parent_id,
last_link_name=link_name,
)
link = VorkNodeLink(data=link_data.model_dump())
validated_link = link.validate()
db_node_link = await create_node_link_schema(connection, validated_link, user_validation.id)
links_settings = ProcessSchemaSettingsNodeLink(
id=db_node_link.id,
link_name=db_node_link.link_name,
parent_port_number=db_node_link.link_point_id,
from_id=db_node_link.node_id,
to_id=db_node_link.next_node_id,
)
node_settings = ProcessSchemaSettingsNode(
id=db_ps_node.id,
node_type=db_ps_node.node_type,
data=node_instance_validated.data.model_dump(),
from_node=None,
links=[{"links": links_settings.model_dump()}],
)
settings_dict = {"node": node_settings.model_dump(mode="json")}
await update_process_schema_settings_by_id(connection, db_ps_node.ps_id, settings_dict)
except Exception as e: except Exception as e:
raise create_server_error( raise create_server_error(
message="Failed to create node", message="Failed to create node",
@@ -240,14 +179,4 @@ async def create_ps_node_endpoint(
details={"error": str(e)}, details={"error": str(e)},
) )
ps_node_front_response = Ps_NodeFrontResponse(
description=node_descriptor.model_dump(),
node=Ps_NodeFrontResponseNode(
id=db_ps_node.id,
node_type=db_ps_node.node_type,
data=to_camel_dict(node_instance_validated.data.model_dump()),
),
links=[{"links": links_settings.model_dump()}],
)
return ps_node_front_response return ps_node_front_response

View File

View File

@@ -0,0 +1,67 @@
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncConnection
from orm.tables.account import AccountStatus
from api.db.logic.account import (
get_user_by_id,
update_user_by_id,
get_user_account_page_DTO,
create_user,
)
from api.db.logic.keyring import update_password_key, create_password_key
from api.schemas.account.account import User
from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserFilterDTO, AllUser
class AccountService:
"""Сервис для работы с аккаунтами пользователей"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def list(self, filter_dto: UserFilterDTO) -> Optional[AllUserResponse]:
"""
Получает список пользователей с пагинацией и фильтрацией.
"""
return await get_user_account_page_DTO(self.connection, filter_dto)
async def get(self, user_id: int) -> Optional[User]:
"""
Получает пользователя по ID.
"""
return await get_user_by_id(self.connection, user_id)
async def create(self, user_data: UserCreate, creator_id: int) -> AllUser:
"""
Создаёт нового пользователя.
"""
new_user = await create_user(self.connection, user_data, creator_id)
await create_password_key(self.connection, user_data.password, new_user.id)
return new_user
async def update(self, user_id: int, user_update_data: dict, password: str | None = None) -> User:
"""
Обновляет данные аккаунта пользователя.
"""
if password is not None:
await update_password_key(self.connection, user_id, password)
if user_update_data:
user = await get_user_by_id(self.connection, user_id)
await update_user_by_id(self.connection, user_update_data, user)
updated_user = await get_user_by_id(self.connection, user_id)
return updated_user
async def delete(self, user_id: int) -> User:
"""
Помечает аккаунт пользователя как удалённый.
"""
user = await get_user_by_id(self.connection, user_id)
update_data = {"status": AccountStatus.DELETED.value}
await update_user_by_id(self.connection, update_data, user)
deleted_user = await get_user_by_id(self.connection, user_id)
return deleted_user

View File

@@ -0,0 +1,47 @@
from datetime import datetime, timedelta, timezone
from fastapi_jwt_auth import AuthJWT
from sqlalchemy.ext.asyncio import AsyncConnection
from api.config import get_settings
from api.db.logic.auth import add_new_refresh_token, upgrade_old_refresh_token
from api.schemas.endpoints.account import AllUser
from api.schemas.endpoints.auth import Tokens
class AuthService:
"""Сервис для работы с аутентификацией"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def login(self, user: AllUser, authorize: AuthJWT) -> Tokens:
"""
Создаёт access и refresh токены для пользователя.
"""
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
refresh_token_expires = timedelta(days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS)
access_token = authorize.create_access_token(subject=user.login, expires_time=access_token_expires)
refresh_token = authorize.create_refresh_token(subject=user.login, expires_time=refresh_token_expires)
refresh_token_expires_time = datetime.now(timezone.utc) + refresh_token_expires
await add_new_refresh_token(self.connection, refresh_token, refresh_token_expires_time, user)
return Tokens(access_token=access_token, refresh_token=refresh_token)
async def refresh(self, current_user: str, authorize: AuthJWT) -> Tokens:
"""
Создаёт новый access токен на основе refresh токена.
"""
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
new_access_token = authorize.create_access_token(subject=current_user, expires_time=access_token_expires)
return Tokens(access_token=new_access_token)
async def invalidate_refresh_token(self, refresh_token: str) -> None:
"""
Помечает refresh токен как невалидный.
"""
await upgrade_old_refresh_token(self.connection, refresh_token)

View File

@@ -0,0 +1,68 @@
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncConnection
from orm.tables.account import KeyStatus
from api.db.logic.keyring import get_key_by_id, update_key_by_id, create_key
from api.schemas.account.account_keyring import AccountKeyring
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.error import create_validation_error
from fastapi import status
class KeyringService:
"""Сервис для работы с keyring"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def get(self, key_id: str) -> Optional[AccountKeyring]:
"""
Получает keyring по key_id.
"""
return await get_key_by_id(self.connection, key_id)
async def create(self, key: AccountKeyringUpdate, key_id: str) -> AccountKeyring:
"""
Создаёт новый keyring.
"""
from api.schemas.account.account_keyring import AccountKeyring
from datetime import datetime, timezone
if key.owner_id is None or key.key_type is None or key.key_value is None or key.status is None:
raise create_validation_error(
message="All required fields must be provided for keyring creation",
status_code=status.HTTP_400_BAD_REQUEST,
)
account_keyring = AccountKeyring(
owner_id=key.owner_id,
key_type=key.key_type,
key_value=key.key_value,
status=key.status,
expiry=None,
created_at=datetime.now(timezone.utc),
)
return await create_key(self.connection, account_keyring, key_id)
async def update(self, key_id: str, update_data: dict) -> AccountKeyring:
"""
Обновляет данные keyring.
"""
keyring = await get_key_by_id(self.connection, key_id)
await update_key_by_id(self.connection, update_data, keyring)
updated_keyring = await get_key_by_id(self.connection, key_id)
return updated_keyring
async def delete(self, key_id: str) -> AccountKeyring:
"""
Помечает keyring как удалённый.
"""
keyring = await get_key_by_id(self.connection, key_id)
update_data = {"status": KeyStatus.DELETED.value}
await update_key_by_id(self.connection, update_data, keyring)
deleted_keyring = await get_key_by_id(self.connection, key_id)
return deleted_keyring

View File

@@ -0,0 +1,63 @@
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncConnection
from orm.tables.events import EventStatus
from api.db.logic.list_events import (
get_list_events_by_id,
get_list_events_by_name,
get_list_events_page_DTO,
update_list_events_by_id,
create_list_events,
)
from api.schemas.events.list_events import ListEvent
from api.schemas.endpoints.list_events import AllListEventResponse, ListEventFilterDTO, ListEventUpdate
class ListEventsService:
"""Сервис для работы с list events"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def list(self, filter_dto: ListEventFilterDTO) -> Optional[AllListEventResponse]:
"""
Получает список событий с пагинацией и фильтрацией.
"""
return await get_list_events_page_DTO(self.connection, filter_dto)
async def get(self, list_events_id: int) -> Optional[ListEvent]:
"""
Получает событие по ID.
"""
return await get_list_events_by_id(self.connection, list_events_id)
async def get_by_name(self, name: str) -> Optional[ListEvent]:
"""
Получает событие по name.
"""
return await get_list_events_by_name(self.connection, name)
async def create(self, list_events_data: ListEventUpdate, creator_id: int) -> Optional[ListEvent]:
"""
Создаёт новое событие.
"""
await create_list_events(self.connection, list_events_data, creator_id)
return await get_list_events_by_name(self.connection, list_events_data.name)
async def update(self, list_events_id: int, update_data: dict) -> ListEvent:
"""
Обновляет данные события.
"""
list_events = await get_list_events_by_id(self.connection, list_events_id)
await update_list_events_by_id(self.connection, update_data, list_events)
return await get_list_events_by_id(self.connection, list_events_id)
async def delete(self, list_events_id: int) -> ListEvent:
"""
Помечает событие как удалённое.
"""
list_events = await get_list_events_by_id(self.connection, list_events_id)
update_data = {"status": EventStatus.DELETED.value}
await update_list_events_by_id(self.connection, update_data, list_events)
return await get_list_events_by_id(self.connection, list_events_id)

View File

@@ -0,0 +1,110 @@
from typing import Optional, Dict, Any
from sqlalchemy.ext.asyncio import AsyncConnection
from orm.tables.process import ProcessStatus
from api.db.logic.process_schema import (
get_process_schema_by_id,
get_process_schema_page_DTO,
update_process_schema_by_id,
create_process_schema,
update_process_schema_settings_by_id,
)
from api.db.logic.ps_node import create_ps_node_schema
from api.schemas.process.process_schema import ProcessSchema, ProcessSchemaSettingsNode
from api.schemas.endpoints.process_schema import AllProcessSchemaResponse, ProcessSchemaFilterDTO, ProcessSchemaUpdate
from api.schemas.process.ps_node import Ps_NodeFrontResponse, Ps_NodeFrontResponseNode
from orm.tables.process import NodeType
from core import VorkNodeRegistry
from model_nodes import ListenNodeData
from api.utils.node_counter import increment_node_counter
class ProcessSchemaService:
"""Сервис для работы с process schema"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def list(self, filter_dto: ProcessSchemaFilterDTO) -> Optional[AllProcessSchemaResponse]:
"""
Получает список схем процессов с пагинацией и фильтрацией.
"""
return await get_process_schema_page_DTO(self.connection, filter_dto)
async def get(self, process_schema_id: int) -> Optional[ProcessSchema]:
"""
Получает схему процесса по ID.
"""
return await get_process_schema_by_id(self.connection, process_schema_id)
async def create(self, creator_id: int) -> Dict[str, Any]:
"""
Создаёт новую схему процесса с начальной нодой LISTEN.
"""
current_node_counter = increment_node_counter()
title = f"Новая схема {current_node_counter}"
description = "Default description"
node_id = await create_process_schema(self.connection, creator_id, title, description)
process_schema_new = await get_process_schema_by_id(self.connection, node_id)
start_node_data = ListenNodeData(ps_id=process_schema_new.id, node_type=NodeType.START.value, is_start="True")
start_node_links = {}
registery = VorkNodeRegistry()
vork_node = registery.get("LISTEN")
node_descriptor = vork_node.form()
start_node = vork_node(data=start_node_data.model_dump(), links=start_node_links)
validated_start_schema = start_node.validate()
db_start_schema = await create_ps_node_schema(self.connection, validated_start_schema, creator_id)
node = ProcessSchemaSettingsNode(
id=db_start_schema.id,
node_type=NodeType.LISTEN.value,
data=validated_start_schema.data.model_dump(),
from_node=None,
links=None,
)
settings_dict = {"node": node.model_dump(mode="json")}
await update_process_schema_settings_by_id(self.connection, process_schema_new.id, settings_dict)
process_schema_new = await get_process_schema_by_id(self.connection, node_id)
ps_node_front_response = Ps_NodeFrontResponse(
description=node_descriptor.model_dump(),
node=Ps_NodeFrontResponseNode(
id=db_start_schema.id, node_type=NodeType.LISTEN.value, data=validated_start_schema.data.model_dump()
),
link=None,
)
response_data = {
"process_schema": process_schema_new.model_dump(),
"node_listen": ps_node_front_response.model_dump(),
}
return response_data
async def update(self, process_schema_id: int, update_data: dict, process_schema: ProcessSchema) -> ProcessSchema:
"""
Обновляет данные схемы процесса.
"""
await update_process_schema_by_id(self.connection, update_data, process_schema)
return await get_process_schema_by_id(self.connection, process_schema_id)
async def delete(self, process_schema_id: int, process_schema: ProcessSchema) -> None:
"""
Помечает схему процесса как удалённую.
"""
process_schema_update = ProcessSchemaUpdate(status=ProcessStatus.DELETED.value)
update_data = process_schema_update.model_dump(by_alias=True, exclude_none=True)
await update_process_schema_by_id(self.connection, update_data, process_schema)

View File

@@ -0,0 +1,30 @@
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.logic.account import get_user_by_id, get_user_by_login, update_user_by_id
from api.schemas.account.account import User
class ProfileService:
"""Сервис для работы с профилем пользователя"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def get_by_login(self, login: str) -> Optional[User]:
"""
Получает пользователя по логину.
"""
return await get_user_by_login(self.connection, login)
async def update(self, user_id: int, update_data: dict, user: User) -> User:
"""
Обновляет данные профиля пользователя.
"""
if update_data is None or not update_data:
return user
await update_user_by_id(self.connection, update_data, user)
updated_user = await get_user_by_id(self.connection, user_id)
return updated_user

View File

@@ -0,0 +1,92 @@
from typing import List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.logic.ps_node import (
get_nodes_for_deletion_ordered,
delete_ps_nodes_delete_handler,
create_ps_node_schema,
)
from api.db.logic.node_link import get_last_link_name_by_node_id, create_node_link_schema
from api.db.logic.process_schema import update_process_schema_settings_by_id
from api.schemas.process.process_schema import ProcessSchemaSettingsNodeLink, ProcessSchemaSettingsNode
from api.schemas.process.ps_node import Ps_NodeFrontResponse, Ps_NodeFrontResponseNode
from core import VorkNodeRegistry, VorkNodeLink
from model_nodes import VorkNodeLinkData
from api.utils.to_camel_dict import to_camel_dict
class PsNodeService:
"""Сервис для работы с ps nodes"""
def __init__(self, connection: AsyncConnection):
self.connection = connection
async def delete(self, next_node_id: int) -> Dict[str, List[int]]:
"""
Удаляет ноды в правильном порядке.
"""
ordered_node_ids = await get_nodes_for_deletion_ordered(self.connection, next_node_id)
deleted_node_ids = await delete_ps_nodes_delete_handler(self.connection, ordered_node_ids)
return {
"deleted_node_ids": deleted_node_ids,
}
async def create(
self, ps_node_data: Dict[str, Any], links: Dict[str, Any], creator_id: int
) -> Ps_NodeFrontResponse:
"""
Создаёт новую ноду с линком и обновляет настройки схемы процесса.
"""
registery = VorkNodeRegistry()
vork_node = registery.get(ps_node_data["node_type"])
node_descriptor = vork_node.form()
node_instance = vork_node(data=ps_node_data, links=links)
node_instance_validated = node_instance.validate()
db_ps_node = await create_ps_node_schema(self.connection, node_instance_validated, creator_id)
link_name = await get_last_link_name_by_node_id(self.connection, db_ps_node.ps_id)
link_data = VorkNodeLinkData(
parent_port_number=node_instance_validated.parent_port_number,
to_id=db_ps_node.id,
from_id=node_instance_validated.parent_id,
last_link_name=link_name,
)
link = VorkNodeLink(data=link_data.model_dump())
validated_link = link.validate()
db_node_link = await create_node_link_schema(self.connection, validated_link, creator_id)
links_settings = ProcessSchemaSettingsNodeLink(
id=db_node_link.id,
link_name=db_node_link.link_name,
parent_port_number=db_node_link.link_point_id,
from_id=db_node_link.node_id,
to_id=db_node_link.next_node_id,
)
node_settings = ProcessSchemaSettingsNode(
id=db_ps_node.id,
node_type=db_ps_node.node_type,
data=node_instance_validated.data.model_dump(),
from_node=None,
links=[{"links": links_settings.model_dump()}],
)
settings_dict = {"node": node_settings.model_dump(mode="json")}
await update_process_schema_settings_by_id(self.connection, db_ps_node.ps_id, settings_dict)
ps_node_front_response = Ps_NodeFrontResponse(
description=node_descriptor.model_dump(),
node=Ps_NodeFrontResponseNode(
id=db_ps_node.id,
node_type=db_ps_node.node_type,
data=to_camel_dict(node_instance_validated.data.model_dump()),
),
links=[{"links": links_settings.model_dump()}],
)
return ps_node_front_response

View File

@@ -11,7 +11,7 @@ class UserRoleValidator:
self.connection = connection self.connection = connection
async def validate_admin(self, current_user: int): async def validate_admin(self, current_user: int):
"""Проверяет права администратора или владельца""" """Проверяет права пользователя"""
try: try:
authorize_user = await get_user_by_login(self.connection, current_user) authorize_user = await get_user_by_login(self.connection, current_user)
except Exception as e: except Exception as e: