Merge pull request 'feat: CRUD ListEvent' (#14) from VORKOUT-14 into master

Reviewed-on: #14
Reviewed-by: Vladislav Syrochkin <vlad.dev@heado.ru>
This commit is contained in:
ivan.dev 2025-07-04 18:06:03 +05:00
commit d03600b23d
10 changed files with 438 additions and 24 deletions

View File

@ -0,0 +1,174 @@
from typing import Optional
import math
from datetime import datetime, timezone
from sqlalchemy import insert, select, func
from sqlalchemy.ext.asyncio import AsyncConnection
from enum import Enum
from api.db.tables.events import list_events_table
from api.schemas.events.list_events import ListEvent
from api.schemas.endpoints.list_events import all_list_event_adapter,AllListEventResponse
async def get_listevents_page_by_creator_id(connection: AsyncConnection, creator_id: int, page: int, limit: int) -> Optional[AllListEventResponse]:
"""
Получает список событий заданного создателя по значениям page и limit и creator_id.
"""
first_event = page * limit - limit
query = (
select(
list_events_table.c.id,
list_events_table.c.name,
list_events_table.c.title,
list_events_table.c.creator_id,
list_events_table.c.created_at,
list_events_table.c.schema_,
list_events_table.c.state,
list_events_table.c.status,
)
.where(list_events_table.c.creator_id == creator_id) # Фильтрация по creator_id
.order_by(list_events_table.c.id)
.offset(first_event)
.limit(limit)
)
count_query = (
select(func.count())
.select_from(list_events_table)
.where(list_events_table.c.creator_id == creator_id) # Фильтрация по creator_id
)
result = await connection.execute(query)
count_result = await connection.execute(count_query)
events_data = result.mappings().all()
total_count = count_result.scalar()
total_pages = math.ceil(total_count / limit)
# Здесь предполагается, что all_list_event_adapter.validate_python корректно обрабатывает данные
validated_list_event = all_list_event_adapter.validate_python(events_data)
return AllListEventResponse(list_event=validated_list_event, amount_count=total_count, amount_pages=total_pages, current_page=page,
limit = limit)
async def get_listevents_page(connection: AsyncConnection, page, limit) -> Optional[AllListEventResponse]:
"""
Получает список событий заданного создателя по значениям page и limit.
"""
first_event = page * limit - (limit)
query = (
select(
list_events_table.c.id,
list_events_table.c.name,
list_events_table.c.title,
list_events_table.c.creator_id,
list_events_table.c.created_at,
list_events_table.c.schema,
list_events_table.c.state,
list_events_table.c.status,
)
.order_by(list_events_table.c.id)
.offset(first_event)
.limit(limit)
)
count_query = select(func.count()).select_from(list_events_table)
result = await connection.execute(query)
count_result = await connection.execute(count_query)
events_data = result.mappings().all()
total_count = count_result.scalar()
total_pages = math.ceil(total_count / limit)
# Здесь предполагается, что all_list_event_adapter.validate_python корректно обрабатывает данные
validated_list_event = all_list_event_adapter.validate_python(events_data)
return AllListEventResponse(list_event=validated_list_event, amount_count=total_count, amount_pages=total_pages,current_page=page,
limit = limit)
async def get_listevents_by_name(connection: AsyncConnection, name: str) -> Optional[ListEvent]:
"""
Получает list events по name.
"""
query = select(list_events_table).where(list_events_table.c.name == name)
listevents_db_cursor = await connection.execute(query)
listevents_db = listevents_db_cursor.one_or_none()
if not listevents_db:
return None
listevents_data = {
column.name: (
getattr(listevents_db, column.name).name
if isinstance(getattr(listevents_db, column.name), Enum)
else getattr(listevents_db, column.name)
)
for column in list_events_table.columns
}
return ListEvent.model_validate(listevents_data)
async def get_listevents_by_id(connection: AsyncConnection, id: int) -> Optional[ListEvent]:
"""
Получает listevent по id.
"""
query = select(list_events_table).where(list_events_table.c.id == id)
listevents_db_cursor = await connection.execute(query)
listevents_db = listevents_db_cursor.one_or_none()
if not listevents_db:
return None
listevents_data = {
column.name: (
getattr(listevents_db, column.name).name
if isinstance(getattr(listevents_db, column.name), Enum)
else getattr(listevents_db, column.name)
)
for column in list_events_table.columns
}
return ListEvent.model_validate(listevents_data)
async def update_listevents_by_id(connection: AsyncConnection, update_values, listevents):
"""
Вносит изменеия в нужное поле таблицы list_events_table.
"""
await connection.execute(list_events_table.update().where(list_events_table.c.id == listevents.id).values(**update_values))
await connection.commit()
async def create_listevents(connection: AsyncConnection, listevents: ListEvent, creator_id: int) -> Optional[ListEvent]:
"""
Создает нове поле в таблице list_events_table.
"""
query = insert(list_events_table).values(
name=listevents.name,
title=listevents.title, # добавлено поле title
creator_id=creator_id,
created_at=datetime.now(timezone.utc),
schema=listevents.schema_, # добавлено поле schema
state=listevents.state.value, # добавлено поле state
status=listevents.status.value # добавлено поле status
)
await connection.execute(query)
await connection.commit()
return listevents

View File

@ -3,8 +3,6 @@ import enum
from sqlalchemy import Table, Column, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index
from sqlalchemy.sql import func
from enum import Enum
from api.db.sql_types import UnsignedInt
from api.db import metadata

View File

@ -2,8 +2,9 @@ from api.endpoints.auth import api_router as auth_router
from api.endpoints.profile import api_router as profile_router
from api.endpoints.account import api_router as account_router
from api.endpoints.keyring import api_router as keyring_router
from api.endpoints.listevents import api_router as listevents_router
list_of_routes = [auth_router, profile_router, account_router, keyring_router]
list_of_routes = [auth_router, profile_router, account_router, keyring_router,listevents_router]
__all__ = [
"list_of_routes",

View File

@ -0,0 +1,174 @@
from fastapi import (
APIRouter,
Depends,
HTTPException,
status,
)
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep
from api.db.logic.account import (
get_user_by_login
)
from api.db.logic.listevents import (
get_listevents_by_name,
get_listevents_by_id,
create_listevents,
update_listevents_by_id,
get_listevents_page,
get_listevents_page_by_creator_id
)
from api.schemas.events.list_events import ListEvent
from api.db.tables.events import EventStatus
from api.schemas.base import bearer_schema
from api.schemas.endpoints.list_events import ListEventUpdate,AllListEventResponse
from api.services.auth import get_current_user
from api.services.user_role_validation import db_user_role_validation_for_listevents_by_listevent_id,db_user_role_validation_for_listevents
from api.services.update_data_validation import update_listevents_data_changes
api_router = APIRouter(
prefix="/listevents",
tags=["list events"],
)
@api_router.get("",
dependencies=[Depends(bearer_schema)],
response_model=AllListEventResponse)
async def get_all_list_events(
page: int = 1,
limit: int = 10,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
authorize_user,page_flag = await db_user_role_validation_for_listevents(connection, current_user)
if page_flag:
list_eventslist = await get_listevents_page(connection, page, limit)
print(list_eventslist)
if list_eventslist is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
return list_eventslist
else:
list_events_list = await get_listevents_page_by_creator_id(connection,authorize_user.id, page, limit)
if list_events_list is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
return list_events_list
@api_router.get("/{listevents_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
async def get_list_events(
listevents_id: int,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user)
):
listevents_validation = await get_listevents_by_id(connection, listevents_id)
if listevents_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
authorize_user = await db_user_role_validation_for_listevents_by_listevent_id(connection, current_user,listevents_validation.creator_id)
if listevents_id is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
return listevents_validation
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
async def create_list_events(
listevents: ListEventUpdate,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user)
):
user_validation = await get_user_by_login(connection, current_user)
listevents_validation = await get_listevents_by_name(connection, listevents.name)
if listevents_validation is None:
await create_listevents(connection, listevents, user_validation.id)
listevents_new = await get_listevents_by_name(connection, listevents.name)
return listevents_new
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="An List events with this information already exists."
)
@api_router.put("/{listevents_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
async def update_listevents(
listevents_id: int,
listevents_update: ListEventUpdate,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
listevents_validation = await get_listevents_by_id(connection, listevents_id)
if listevents_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
authorize_user = await db_user_role_validation_for_listevents_by_listevent_id(connection, current_user,listevents_validation.creator_id)
update_values = update_listevents_data_changes(listevents_update, listevents_validation)
if update_values is None:
return listevents_validation
listevents_update_data = ListEvent.model_validate({**listevents_validation.model_dump(), **update_values})
await update_listevents_by_id(connection, update_values, listevents_validation)
listevents = await get_listevents_by_id(connection, listevents_id)
return listevents
@api_router.delete("/{listevents_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
async def delete_list_events(
listevents_id: int,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
listevents_validation = await get_listevents_by_id(connection, listevents_id)
if listevents_validation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="List events not found")
authorize_user = await db_user_role_validation_for_listevents_by_listevent_id(connection, current_user,listevents_validation.creator_id)
listevents_update = ListEventUpdate(status=EventStatus.DELETED.value)
update_values = update_listevents_data_changes(listevents_update, listevents_validation)
if update_values is None:
return listevents_validation
await update_listevents_by_id(connection, update_values, listevents_validation)
listevents = await get_listevents_by_id(connection, listevents_id)
return listevents

View File

@ -8,7 +8,6 @@ from api.schemas.base import Base
class UserUpdate(Base):
id: Optional[int] = None
name: Optional[str] = Field(None, max_length=100)
login: Optional[str] = Field(None, max_length=100)
email: Optional[EmailStr] = None
@ -16,8 +15,6 @@ class UserUpdate(Base):
bind_tenant_id: Optional[str] = Field(None, max_length=40)
role: Optional[AccountRole] = None
meta: Optional[dict] = None
creator_id: Optional[int] = None
created_at: Optional[datetime] = None
status: Optional[AccountStatus] = None

View File

@ -10,8 +10,5 @@ from api.schemas.base import Base
class AccountKeyringUpdate(Base):
owner_id: Optional[int] = None
key_type: Optional[KeyType] = None
key_id: Optional[str] = Field(None, max_length=40)
key_value: Optional[str] = Field(None, max_length=255)
created_at: Optional[datetime] = None
expiry: Optional[datetime] = None
status: Optional[KeyStatus] = None

View File

@ -0,0 +1,35 @@
from pydantic import Field, TypeAdapter
from typing import Optional,Dict, Any, List
from datetime import datetime
from api.schemas.base import Base
from api.db.tables.events import EventState,EventStatus
class ListEventUpdate(Base):
name: Optional[str] = Field(None, max_length=40)
title: Optional[str] = Field(None, max_length=64)
schema_: Optional[Dict[str, Any]]= Field(None, alias="schema")
state: Optional[EventState]= None
status: Optional[EventStatus]= None
class AllListEvent(Base):
id: int
name: str
title: str
creator_id: int
created_at: datetime
schema_: Dict[str, Any] = Field(default={}, alias="schema")
state: EventState
status: EventStatus
class AllListEventResponse(Base):
list_event: List[AllListEvent]
amount_count: int
amount_pages: int
current_page: int
limit: int
all_list_event_adapter = TypeAdapter(List[AllListEvent])

View File

@ -1,20 +1,9 @@
from pydantic import Field
from typing import Dict, Any
from datetime import datetime
from enum import Enum
from api.schemas.base import Base
class State(Enum):
AUTO = "Auto"
DESCRIPTED = "Descripted"
class Status(Enum):
ACTIVE = "Active"
DISABLED = "Disabled"
DELETED = "Deleted"
from api.db.tables.events import EventState,EventStatus
class ListEvent(Base):
@ -23,6 +12,6 @@ class ListEvent(Base):
title: str = Field(..., max_length=64)
creator_id: int
created_at: datetime
schema: Dict[str, Any]
state: State
status: Status
schema_: Dict[str, Any] = Field(..., alias="schema")
state: EventState
status: EventStatus

View File

@ -4,6 +4,8 @@ from api.schemas.endpoints.account import UserUpdate
from api.db.tables.account import KeyType, KeyStatus
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.db.tables.account import AccountRole, AccountStatus
from api.schemas.endpoints.list_events import ListEventUpdate
from api.db.tables.events import EventState, EventStatus
def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]:
@ -72,3 +74,36 @@ def update_key_data_changes(update_data: AccountKeyringUpdate, key) -> Optional[
changes[field] = new_value
return changes if changes else None
def update_listevents_data_changes(update_data: ListEventUpdate, listevents) -> Optional[dict]:
"""
Сравнивает данные для обновления с текущими значениями listevents.
Возвращает:
- None, если нет изменений
- Словарь {поле: новое_значение} для измененных полей
"""
update_values = {}
changes = {}
for field, value in update_data.model_dump(exclude_unset=True).items():
if value is None:
continue
if isinstance(value, (EventState, EventStatus)):
update_values[field] = value.value
else:
update_values[field] = value
for field, new_value in update_values.items():
if not hasattr(listevents, field):
continue
current_value = getattr(listevents, field)
if isinstance(current_value, Enum):
current_value = current_value.value
if current_value != new_value:
changes[field] = new_value
return changes if changes else None

View File

@ -11,3 +11,17 @@ async def db_user_role_validation(connection, current_user):
if authorize_user.role not in {AccountRole.OWNER, AccountRole.ADMIN}:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have enough permissions")
return authorize_user
async def db_user_role_validation_for_listevents_by_listevent_id(connection, current_user,current_listevents_creator_id):
authorize_user = await get_user_by_login(connection, current_user)
if authorize_user.role not in {AccountRole.OWNER, AccountRole.ADMIN}:
if authorize_user.id != current_listevents_creator_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have enough permissions")
return authorize_user
async def db_user_role_validation_for_listevents(connection, current_user):
authorize_user = await get_user_by_login(connection, current_user)
if authorize_user.role not in {AccountRole.OWNER, AccountRole.ADMIN}:
return authorize_user,False
else:
return authorize_user,True