WIP: feat: CRUD ListEvent #14

Draft
ivan.dev wants to merge 7 commits from VORKOUT-14 into master
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 = (
ivan.dev marked this conversation as resolved
Review

Можно убрать, если надо логировать, то лучше через logger

Можно убрать, если надо логировать, то лучше через `logger`
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.
"""
ivan.dev marked this conversation as resolved
Review

Тут, видимо, из другого метода скопировал случайно

Тут, видимо, из другого метода скопировал случайно
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)
ivan.dev marked this conversation as resolved
Review

Тоже returning type не соответствует

Тоже returning type не соответствует
async def update_listevents_by_id(connection: AsyncConnection, update_values, listevents):
"""
Вносит изменеия в нужное поле таблицы list_events_table.
"""
ivan.dev marked this conversation as resolved
Review

Тоже из другого метода описание

Тоже из другого метода описание
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 import Table, Column, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index
from sqlalchemy.sql import func from sqlalchemy.sql import func
from enum import Enum
from api.db.sql_types import UnsignedInt from api.db.sql_types import UnsignedInt
from api.db import metadata 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.profile import api_router as profile_router
from api.endpoints.account import api_router as account_router from api.endpoints.account import api_router as account_router
from api.endpoints.keyring import api_router as keyring_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__ = [ __all__ = [
"list_of_routes", "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,15 +8,12 @@ from api.schemas.base import Base
class UserUpdate(Base): class UserUpdate(Base):
id: Optional[int] = None
name: Optional[str] = Field(None, max_length=100) name: Optional[str] = Field(None, max_length=100)
login: Optional[str] = Field(None, max_length=100) login: Optional[str] = Field(None, max_length=100)
email: Optional[EmailStr] = None email: Optional[EmailStr] = None
bind_tenant_id: Optional[str] = Field(None, max_length=40) bind_tenant_id: Optional[str] = Field(None, max_length=40)
role: Optional[AccountRole] = None role: Optional[AccountRole] = None
meta: Optional[dict] = None meta: Optional[dict] = None
creator_id: Optional[int] = None
created_at: Optional[datetime] = None
status: Optional[AccountStatus] = None status: Optional[AccountStatus] = None

View File

@ -10,8 +10,5 @@ from api.schemas.base import Base
class AccountKeyringUpdate(Base): class AccountKeyringUpdate(Base):
owner_id: Optional[int] = None owner_id: Optional[int] = None
key_type: Optional[KeyType] = None key_type: Optional[KeyType] = None
key_id: Optional[str] = Field(None, max_length=40)
key_value: Optional[str] = Field(None, max_length=255) key_value: Optional[str] = Field(None, max_length=255)
created_at: Optional[datetime] = None
expiry: Optional[datetime] = None
status: Optional[KeyStatus] = 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
ivan.dev marked this conversation as resolved
Review

Тут еще надо добавить current_page и limit, можешь посмотреть вот тут

Тут еще надо добавить `current_page` и `limit`, можешь посмотреть вот [тут](https://gitea.heado.ru/Vorkout/connect/src/branch/VORKOUT-8/api/api/db/logic/account.py#L48)
current_page: int
limit: int
all_list_event_adapter = TypeAdapter(List[AllListEvent])

View File

@ -1,20 +1,9 @@
from pydantic import Field from pydantic import Field
from typing import Dict, Any from typing import Dict, Any
from datetime import datetime from datetime import datetime
from enum import Enum
from api.schemas.base import Base from api.schemas.base import Base
from api.db.tables.events import EventState,EventStatus
class State(Enum):
AUTO = "Auto"
DESCRIPTED = "Descripted"
class Status(Enum):
ACTIVE = "Active"
DISABLED = "Disabled"
DELETED = "Deleted"
class ListEvent(Base): class ListEvent(Base):
@ -23,6 +12,6 @@ class ListEvent(Base):
title: str = Field(..., max_length=64) title: str = Field(..., max_length=64)
creator_id: int creator_id: int
created_at: datetime created_at: datetime
schema: Dict[str, Any] schema_: Dict[str, Any] = Field(..., alias="schema")
state: State state: EventState
status: Status 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.db.tables.account import KeyType, KeyStatus
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.db.tables.account import AccountRole, AccountStatus 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]: 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 changes[field] = new_value
return changes if changes else None 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}: 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") raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have enough permissions")
return authorize_user 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