diff --git a/rental_backend/routes/event.py b/rental_backend/routes/event.py index 8dde984..1519d64 100644 --- a/rental_backend/routes/event.py +++ b/rental_backend/routes/event.py @@ -7,7 +7,6 @@ from rental_backend.models.db import Event from rental_backend.schemas.models import EventGet - event = APIRouter(prefix="/event", tags=["Event"]) @@ -19,14 +18,28 @@ async def get_events( user=Depends(UnionAuth(scopes=["rental.event.view"], auto_error=False)), ) -> list[EventGet]: """ - Retrieves a list of events, with optional filtering. + Возвращает список событий с возможностью фильтрации. + + Эндпоинт позволяет получить все события или отфильтровать их по пользователю, + администратору и сессии аренды. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на просмотр событий + + Скоупы: + - `rental.event.view` - Scopes: `["rental.event.view"]` + Параметры: + - `user_id` — (необязательный) фильтр по идентификатору пользователя + - `admin_id` — (необязательный) фильтр по идентификатору администратора + - `session_id` — (необязательный) фильтр по идентификатору сессии аренды - - **admin_id**: Filter events by admin ID. - - **session_id**: Filter events by session ID. + Возвращает: + - список объектов `Event` - Returns a list of events. + Ошибки: + - отсутствуют """ query = db.session.query(Event) if user_id is not None: diff --git a/rental_backend/routes/item.py b/rental_backend/routes/item.py index f486142..5fb9346 100644 --- a/rental_backend/routes/item.py +++ b/rental_backend/routes/item.py @@ -10,7 +10,6 @@ from rental_backend.settings import Settings, get_settings from rental_backend.utils.action import ActionLogger - settings: Settings = get_settings() item = APIRouter(prefix="/item", tags=["Items"]) @@ -18,11 +17,22 @@ @item.get("", response_model=list[ItemGet]) async def get_items(type_id: int = Query(None), user=Depends(UnionAuth())) -> list[ItemGet]: """ - Retrieves a list of items. If `type_id` is specified, only items of that type are returned. + Возвращает список предметов. При указании `type_id` возвращаются только предметы заданного типа. + + Условия: + - Пользователь должен быть аутентифицирован + + Скоупы: + - отсутствуют (доступно любому авторизованному пользователю) + + Параметры: + - `type_id` — (необязательный) идентификатор типа предмета для фильтрации - - **type_id**: The ID of the item type (optional). + Возвращает: + - список объектов `Item` - Returns a list of items. + Ошибки: + - отсутствуют """ query = Item.query(session=db.session) if type_id is not None: @@ -34,15 +44,27 @@ async def get_items(type_id: int = Query(None), user=Depends(UnionAuth())) -> li @item.post("", response_model=ItemGet) async def create_item(item: ItemPost, user=Depends(UnionAuth(scopes=["rental.item.create"]))) -> ItemGet: """ - Creates a new item. + Создает новый предмет. - Scopes: `["rental.item.create"]` + Перед созданием проверяется, существует ли тип предмета с указанным `type_id`. + После успешного создания действие логируется как `CREATE_ITEM`. - - **item**: The data for the new item. + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на создание предметов + - Тип предмета с указанным `type_id` должен существовать - Returns the created item. + Скоупы: + - `rental.item.create` - Raises **ObjectNotFound** if the item type with the specified `type_id` is not found. + Параметры: + - `item` — данные нового предмета + + Возвращает: + - созданный объект `Item` + + Ошибки: + - `ObjectNotFound` — тип предмета с указанным `type_id` не найден """ item_type = ItemType.get(item.type_id, session=db.session) if item_type is None: @@ -65,16 +87,28 @@ async def update_item( user=Depends(UnionAuth(scopes=["rental.item.patch"])), ) -> ItemGet: """ - Updates the availability status of an item by its ID. + Обновляет статус доступности предмета по его идентификатору. + + Эндпоинт позволяет изменить только поле `is_available`. + После успешного обновления действие логируется как `UPDATE_ITEM`. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на изменение предметов + - Предмет с указанным `id` должен существовать - Scopes: `["rental.item.patch"]` + Скоупы: + - `rental.item.patch` - - **id**: The ID of the item. - - **is_available**: The new availability status for the item. + Параметры: + - `id` — идентификатор предмета + - `is_available` — новое значение доступности предмета - Returns the updated item. + Возвращает: + - обновленный объект `Item` - Raises **ObjectNotFound** if the item with the specified ID is not found. + Ошибки: + - `ObjectNotFound` — предмет с указанным `id` не найден """ item = Item.query(session=db.session).filter(Item.id == id).one_or_none() if item is not None: @@ -95,15 +129,34 @@ async def delete_item( id: int, user=Depends(UnionAuth(scopes=["rental.item.delete"], allow_none=False)) ) -> StatusResponseModel: """ - Deletes an item by its ID. + Удаляет предмет по его идентификатору. - Scopes: `["rental.item.delete"]` + Перед удалением проверяется, что с предметом не связано активных, + зарезервированных или просроченных сессий аренды. + Если такие сессии существуют — удаление запрещено. - - **id**: The ID of the item. + При успешном удалении: + - удаляется сам предмет + - удаляются связанные сессии аренды (если они не помечены как удалённые) + - удаляются связанные страйки и события + - действие логируется как `DELETE_ITEM` - Returns a status response. + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на удаление предметов + - У предмета не должно быть сессий в статусах ACTIVE / RESERVED / OVERDUE - Raises **ObjectNotFound** if the item with the specified ID is not found. + Скоупы: + - `rental.item.delete` + + Параметры: + - `id` — идентификатор предмета + + Возвращает: + - объект `StatusResponseModel` со статусом удаления + + Ошибки: + - `ObjectNotFound` — предмет не найден или удаление запрещено из-за активных сессий """ rental_sessions = db.session.query(RentalSession).filter(RentalSession.item_id == id) session = rental_sessions.filter( @@ -134,7 +187,22 @@ async def delete_item( @item.get("/{id}", response_model=ItemGet) async def get_item(id: int) -> ItemGet: """ - Получает предмет по его идентификатору. + Возвращает предмет по его идентификатору. + + Условия: + - отсутствуют (доступно без авторизации) + + Скоупы: + - отсутствуют + + Параметры: + - `id` — идентификатор предмета + + Возвращает: + - объект `Item` + + Ошибки: + - возможна ошибка валидации, если предмет с указанным `id` не найден """ item = Item.get(id=id, session=db.session) return ItemGet.model_validate(item) diff --git a/rental_backend/routes/item_type.py b/rental_backend/routes/item_type.py index 65964b4..6ab8302 100644 --- a/rental_backend/routes/item_type.py +++ b/rental_backend/routes/item_type.py @@ -15,7 +15,6 @@ from rental_backend.settings import Settings, get_settings from rental_backend.utils.action import ActionLogger - settings: Settings = get_settings() item_type = APIRouter(prefix="/itemtype", tags=["ItemType"]) @@ -64,13 +63,30 @@ def _calculate_cool_down_end_ts_for_types( @item_type.get("/{id}", response_model=ItemTypeGet, dependencies=[Depends(check_sessions_expiration)]) async def get_item_type(id: int, user=Depends(UnionAuth())) -> ItemTypeGet: """ - Retrieves information about an item type by its ID. + Возвращает информацию о типе предмета по его идентификатору. + + Перед выполнением запроса производится проверка истекших сессий аренды. + В ответ дополнительно включается: + - текущая доступность предметов данного типа для пользователя + - время окончания cooldown (если действует ограничение на создание сессий) + + Условия: + - Пользователь должен быть аутентифицирован + - Тип предмета с указанным `id` должен существовать - - **id**: The ID of the item type. + Скоупы: + - отсутствуют (доступно любому аутентифицированному пользователю) - Returns the item type information. + Параметры: + - `id` — идентификатор типа предмета - Raises **ObjectNotFound** if the item type with the specified ID is not found. + Возвращает: + - объект `ItemType` с дополнительными полями: + - `availability` — доступность предметов данного типа для пользователя + - `cool_down_end_ts` — время окончания cooldown (если применимо) + + Ошибки: + - `ObjectNotFound` — тип предмета с указанным `id` не найден """ item_type: ItemType = ItemType.query(session=db.session).filter(ItemType.id == id).one_or_none() @@ -86,11 +102,32 @@ async def get_item_type(id: int, user=Depends(UnionAuth())) -> ItemTypeGet: @item_type.get("", response_model=list[ItemTypeGet], dependencies=[Depends(check_sessions_expiration)]) async def get_items_types(user=Depends(UnionAuth(auto_error=False))) -> list[ItemTypeGet]: """ - Retrieves a list of all item types. + Возвращает список всех типов предметов. + + Перед выполнением запроса производится проверка истекших сессий аренды. + Для каждого типа предмета в ответ дополнительно рассчитываются: + - текущее количество доступных предметов + - доступность для пользователя + - время окончания cooldown (если действует ограничение на создание сессий) - Returns a list of all item types. + Условия: + - Авторизация не обязательна + - В системе должен существовать хотя бы один тип предмета - Raises **ObjectNotFound** if no item types are found. + Скоупы: + - отсутствуют + + Параметры: + - отсутствуют + + Возвращает: + - список объектов `ItemType` с дополнительными полями: + - `available_items_count` — количество доступных предметов данного типа + - `availability` — доступность предметов данного типа для пользователя + - `cool_down_end_ts` — время окончания cooldown (если применимо) + + Ошибки: + - `ObjectNotFound` — в системе не найдено ни одного типа предмета """ item_types_all: list[ItemType] = ItemType.query(session=db.session).all() if not item_types_all: @@ -126,13 +163,25 @@ async def create_item_type( user=Depends(UnionAuth(scopes=["rental.item_type.create"], allow_none=False)), ) -> ItemTypeGet: """ - Creates a new item type. + Создает новый тип предмета. + + После успешного создания действие логируется как `CREATE_ITEM_TYPE`. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на создание типов предметов - Scopes: `["rental.item_type.create"]` + Скоупы: + - `rental.item_type.create` - - **item_type_info**: The data for the new item type. + Параметры: + - `item_type_info` — данные нового типа предмета - Returns the created item type. + Возвращает: + - созданный объект `ItemType` + + Ошибки: + - отсутствуют """ new_item_type = ItemType.create(session=db.session, **item_type_info.model_dump()) ActionLogger.log_event( @@ -150,16 +199,28 @@ async def update_item_type( id: int, item_type_info: ItemTypePost, user=Depends(UnionAuth(scopes=["rental.item_type.update"], allow_none=False)) ) -> ItemTypeGet: """ - Updates the information of an item type by its ID. + Обновляет тип предмета по его идентификатору. + + Перед обновлением проверяется, существует ли тип предмета с указанным `id`. + После успешного обновления действие логируется как `UPDATE_ITEM_TYPE`. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на изменение типов предметов + - Тип предмета с указанным `id` должен существовать - Scopes: `["rental.item_type.update"]` + Скоупы: + - `rental.item_type.update` - - **id**: The ID of the item type. - - **item_type_info**: The data to update the item type with. + Параметры: + - `id` — идентификатор типа предмета + - `item_type_info` — новые данные типа предмета - Returns the updated item type. + Возвращает: + - обновленный объект `ItemType` - Raises **ObjectNotFound** if the item type with the specified ID is not found. + Ошибки: + - `ObjectNotFound` — тип предмета с указанным `id` не найден """ item_type_to_update = ItemType.get(id, session=db.session) if item_type_to_update is None: @@ -180,19 +241,38 @@ async def make_item_type_available( id: int, count: int, user=Depends(UnionAuth(scopes=["rental.item_type.update"], allow_none=False)) ) -> ItemTypeAvailable: """ - Делает один предмет доступным по ID типа предмета. - - Скоупы: `["rental.item_type.update"]` - - - **id**: ID типа предмета. - - **count**: Абсолютное количество предметов, которые нужно сделать доступными. - Если доступных меньше, делает больше доступных. Если доступных больше, делает меньше доступных. - Если нет возможности сделать count доступных, делает доступным максимально возможное количество. - Возвращает id всех возвращенных предметов и их количество. - - - - Вызывает **ObjectNotFound**, если тип предмета с указанным ID не найден. + Изменяет количество доступных предметов для указанного типа. + + Эндпоинт устанавливает целевое количество доступных предметов (`count`). + Если текущих доступных предметов больше — лишние становятся недоступными. + Если меньше — недостающие предметы делаются доступными. + Если невозможно достичь точного значения (например, недостаточно предметов), + устанавливается максимально возможное количество. + + В обработке участвуют только предметы, не занятые активными или зарезервированными сессиями. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на изменение типов предметов + - Тип предмета с указанным `id` должен существовать + - `count` должен быть неотрицательным + + Скоупы: + - `rental.item_type.update` + + Параметры: + - `id` — идентификатор типа предмета + - `count` — целевое количество доступных предметов + + Возвращает: + - объект `ItemTypeAvailable` + - `item_ids` — список измененных предметов + - `items_changed` — количество измененных предметов + - `total_available` — итоговое количество доступных предметов + + Ошибки: + - `ObjectNotFound` — тип предмета с указанным `id` не найден + - `ValueError` — передано отрицательное значение `count` """ if count < 0: raise ValueError(count) @@ -246,17 +326,29 @@ async def delete_item_type( id: int, user=Depends(UnionAuth(scopes=["rental.item_type.delete"], allow_none=False)) ) -> StatusResponseModel: """ - Deletes an item type by its ID. + Удаляет тип предмета по его идентификатору. + + Удаление возможно только в том случае, если тип предмета существует и с ним не связано ни одного предмета. + После успешного удаления действие логируется как `DELETE_ITEM_TYPE`. - Scopes: `["rental.item_type.delete"]` + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на удаление типов предметов + - Тип предмета с указанным `id` должен существовать + - У типа предмета не должно быть связанных предметов - - **id**: The ID of the item type. + Скоупы: + - `rental.item_type.delete` - Returns a status response. + Параметры: + - `id` — идентификатор типа предмета - Raises **ObjectNotFound** if the item type with the specified ID is not found. + Возвращает: + - объект `StatusResponseModel` со статусом удаления - Raises **ForbiddenAction** if the item type with the specified ID has items. + Ошибки: + - `ObjectNotFound` — тип предмета с указанным `id` не найден + - `ForbiddenAction` — тип предмета нельзя удалить, так как с ним связаны предметы """ item_type_to_delete = ItemType.get(id, session=db.session) if item_type_to_delete is None: diff --git a/rental_backend/routes/rental_session.py b/rental_backend/routes/rental_session.py index 41c657e..46f14cb 100644 --- a/rental_backend/routes/rental_session.py +++ b/rental_backend/routes/rental_session.py @@ -26,7 +26,6 @@ from rental_backend.settings import Settings, get_settings from rental_backend.utils.action import ActionLogger - settings: Settings = get_settings() rental_session = APIRouter(prefix="/rental-sessions", tags=["RentalSession"]) @@ -88,13 +87,26 @@ async def create_rental_session( user=Depends(UnionAuth(scopes=["rental.session.create"], enable_userdata=True)), ): """ - Создает новую сессию аренды для указанного типа предмета. + Создает новую сессию аренды для указанного типа предмета и резервирует его. + + Условия: + - У пользователя нет активной / зарезервированной / просроченной сессии + - Есть доступный предмет + - Не превышен лимит на частоту создания сессий + + Скоупы: + - `rental.session.create` - Cкоупы: `["rental.session.create"]` + Параметры: + - `item_type_id` — идентификатор типа предмета - :param item_type_id: Идентификатор типа предмета. - :raises NoneAvailable: Если нет доступных предметов указанного типа. - :raises SessionExists: Если у пользователя уже есть сессия с указанным типом предмета. + Возвращает: + - созданную `RentalSession` + + Ошибки: + - `SessionExists` — уже есть активная сессия + - `NoneAvailable` — нет доступных предметов + - `RateLimiterError` — превышен лимит """ exist_session_item: list[RentalSession] = RentalSession.query(session=db.session).filter( RentalSession.user_id == user.get("id"), RentalSession.item_type_id == item_type_id @@ -179,15 +191,30 @@ async def start_rental_session( session_id: int, deadline_ts=Depends(validate_deadline_ts), user=Depends(UnionAuth(scopes=["rental.session.admin"])) ): """ - Starts a rental session, changing its status to ACTIVE. + Запускает сессию аренды и переводит её из статуса `RESERVED` в `ACTIVE`. + + После запуска: + - устанавливается время начала аренды (`start_ts`) + - сохраняется идентификатор администратора (`admin_open_id`) + - устанавливается дедлайн возврата (`deadline_ts`), если он не был передан - Scopes: `["rental.session.admin"]` + Условия: + - Сессия должна существовать + - Сессия должна находиться в статусе `RESERVED` - - **session_id**: The ID of the rental session to start. + Скоупы: + - `rental.session.admin` - Returns the updated rental session. + Параметры: + - `session_id` — идентификатор сессии аренды + - `deadline_ts` — (опционально) дедлайн возврата, если не указан — вычисляется автоматически - Raises **ObjectNotFound** if the session with the specified ID is not found. + Возвращает: + - обновленную `RentalSession` со статусом `ACTIVE` + + Ошибки: + - `ObjectNotFound` — сессия с указанным id не найдена + - `ForbiddenAction` — сессию нельзя запустить (неверный статус) """ session: RentalSession = RentalSession.get(session_id, session=db.session) if not session: @@ -233,19 +260,36 @@ async def accept_end_rental_session( user=Depends(UnionAuth(scopes=["rental.session.admin"])), ): """ - Ends a rental session, changing its status to RETURNED. Issues a strike if specified. + Завершает сессию аренды и переводит её в статус `RETURNED`. + + После завершения: + - устанавливается фактическое время возврата (`actual_return_ts`) + - сохраняется идентификатор администратора, завершившего аренду (`admin_close_id`) + - предмет снова становится доступным для аренды (`item.is_available = True`) + + Дополнительно можно выдать страйк пользователю: + - если `with_strike=True`, создается новый страйк + - причина страйка передается через `strike_reason` + - созданный страйк привязывается к завершенной сессии + + Условия: + - Сессия должна существовать + - Сессия должна находиться в статусе `ACTIVE` или `OVERDUE` - Scopes: `["rental.session.admin"]` + Скоупы: + - `rental.session.admin` - - **session_id**: The ID of the rental session to end. - - **with_strike**: A flag indicating whether to issue a strike. - - **strike_reason**: The reason for the strike. + Параметры: + - `session_id` — идентификатор сессии аренды + - `with_strike` — нужно ли выдать страйк при завершении аренды + - `strike_reason` — причина выдачи страйка - Returns the updated rental session. + Возвращает: + - обновленную `RentalSession` со статусом `RETURNED` - Raises: - - **ObjectNotFound**: If the session with the specified ID is not found. - - **InactiveSession**: If the session is not active. + Ошибки: + - `ObjectNotFound` — сессия с указанным id не найдена + - `InactiveSession` — сессию нельзя завершить, так как она не находится в статусе `ACTIVE` или `OVERDUE` """ rent_session = RentalSession.get(id=session_id, session=db.session) if not rent_session: @@ -299,6 +343,28 @@ async def accept_end_rental_session( dependencies=[Depends(check_sessions_expiration), Depends(check_sessions_overdue)], ) async def get_rental_session(session_id: int, user=Depends(UnionAuth(scopes=["rental.session.admin"]))): + """ + Возвращает сессию аренды по её идентификатору. + + Перед получением выполняется проверка истекших и просроченных сессий, + чтобы вернуть актуальное состояние данных. В ответ также включается + информация о страйке, если он привязан к данной сессии. + + Условия: + - Сессия должна существовать + + Скоупы: + - `rental.session.admin` + + Параметры: + - `session_id` — идентификатор сессии аренды + + Возвращает: + - объект `RentalSession` с данными о сессии и `strike_id`, если он есть + + Ошибки: + - `ObjectNotFound` — сессия не найдена + """ rental_session: RentalSession | None = ( RentalSession.query(session=db.session) .options(joinedload(RentalSession.strike)) @@ -400,19 +466,37 @@ async def get_rental_sessions( user=Depends(UnionAuth(scopes=["rental.session.admin"])), ): """ - Retrieves a list of rental sessions with optional status filtering. - - Scopes: `["rental.session.admin"]` - - - **is_reserved**: Filter by reserved sessions. - - **is_canceled**: Filter by canceled sessions. - - **is_dismissed**: Filter by dismissed sessions. - - **is_overdue**: Filter by overdue sessions. - - **is_returned**: Filter by returned sessions. - - **is_active**: Filter by active sessions. - - **is_expired**: Filter by expired sessions. - - **user_id**: User_id to get sessions - Returns a list of rental sessions. + Возвращает список сессий аренды с возможностью фильтрации по статусам, пользователю и типу предмета. + + Перед выполнением запроса автоматически обновляются истекшие и просроченные сессии, + чтобы вернуть актуальные данные. + + Фильтрация: + - Можно указать один или несколько флагов статусов (`is_reserved`, `is_active` и т.д.) + - Если ни один статус не указан — возвращаются все сессии + - Можно дополнительно отфильтровать по `user_id` и `item_type_id` + + Результат сортируется по статусу и времени создания (reservation_ts). + + Скоупы: + - `rental.session.admin` + + Параметры: + - `is_reserved` — показывать сессии со статусом `RESERVED` + - `is_canceled` — показывать сессии со статусом `CANCELED` + - `is_dismissed` — показывать сессии со статусом `DISMISSED` + - `is_overdue` — показывать сессии со статусом `OVERDUE` + - `is_returned` — показывать сессии со статусом `RETURNED` + - `is_active` — показывать сессии со статусом `ACTIVE` + - `is_expired` — показывать сессии со статусом `EXPIRED` + - `item_type_id` — фильтр по типу предмета + - `user_id` — фильтр по пользователю + + Возвращает: + - список объектов `RentalSession`, удовлетворяющих условиям фильтрации + + Ошибки: + - отсутствуют (пустой список, если ничего не найдено) """ return await get_rental_sessions_common( db_session=db.session, @@ -446,16 +530,33 @@ async def get_my_sessions( user=Depends(UnionAuth()), ): """ - Retrieves a list of rental sessions for the user with optional status filtering. - - - **is_reserved**: Filter by reserved sessions. - - **is_canceled**: Filter by canceled sessions. - - **is_dismissed**: Filter by dismissed sessions. - - **is_overdue**: Filter by overdue sessions. - - **is_returned**: Filter by returned sessions. - - **is_active**: Filter by active sessions. - - **is_expired**: Filter by expired sessions. - Returns a list of rental sessions. + Возвращает список сессий аренды текущего пользователя с возможностью фильтрации. + + Перед выполнением запроса автоматически обновляются истекшие и просроченные сессии, + чтобы вернуть актуальные данные. + + Фильтрация: + - Можно указать один или несколько флагов статусов (`is_reserved`, `is_active` и т.д.) + - Если ни один статус не указан — возвращаются все сессии пользователя + - Можно дополнительно отфильтровать по `item_type_id` + + В отличие от административного эндпоинта, возвращаются только сессии текущего пользователя. + + Параметры: + - `is_reserved` — показывать сессии со статусом `RESERVED` + - `is_canceled` — показывать сессии со статусом `CANCELED` + - `is_dismissed` — показывать сессии со статусом `DISMISSED` + - `is_overdue` — показывать сессии со статусом `OVERDUE` + - `is_returned` — показывать сессии со статусом `RETURNED` + - `is_active` — показывать сессии со статусом `ACTIVE` + - `is_expired` — показывать сессии со статусом `EXPIRED` + - `item_type_id` — фильтр по типу предмета + + Возвращает: + - список объектов `RentalSession`, принадлежащих текущему пользователю + + Ошибки: + - отсутствуют (пустой список, если ничего не найдено) """ return await get_rental_sessions_common( db_session=db.session, @@ -474,16 +575,23 @@ async def get_my_sessions( @rental_session.delete("/{session_id}", response_model=StatusResponseModel) async def delete_rental_session(session_id: int, user=Depends(UnionAuth(scopes=["rental.session.admin"]))): """ - Deletes a session. + Удаляет сессию аренды по её идентификатору. + + Удаление возможно только для завершённых или неактивных сессий. + Сессии в статусах `RESERVED`, `ACTIVE` или `OVERDUE` удалить нельзя. - Scopes: `["rental.session.admin"]` + Скоупы: + - `rental.session.admin` - - **session_id**: The ID of the rental session to delete. + Параметры: + - `session_id` — идентификатор сессии аренды - Returns the deleted rental session. + Возвращает: + - объект `StatusResponseModel` с информацией об успешном удалении - Raises **ForbiddenAction** if the session is in RESERVED, ACTIVE, OVERDUE status. - Raises **ObjectNotFound** if the session does not exist. + Ошибки: + - `ObjectNotFound` — сессия с указанным id не найдена + - `ForbiddenAction` — сессию нельзя удалить (она находится в активном или блокирующем статусе) """ session = RentalSession.get(id=session_id, session=db.session) if ( @@ -505,13 +613,23 @@ async def delete_rental_session(session_id: int, user=Depends(UnionAuth(scopes=[ ) async def cancel_rental_session(session_id: int, user=Depends(UnionAuth())): """ - Cancels a session in the RESERVED status. Can only be canceled by the user who created it. + Отменяет сессию аренды, переводя её в статус `CANCELED`. - - **session_id**: The ID of the rental session to cancel. + Отмена возможна только для сессий в статусе `RESERVED` и только пользователем, + который создал эту сессию. - Returns the canceled rental session. + После отмены: + - устанавливается время завершения (`end_ts`) + - предмет снова становится доступным для аренды (`item.is_available = True`) - Raises **ForbiddenAction** if the user is not the owner or the session is not in RESERVED status. + Параметры: + - `session_id` — идентификатор сессии аренды + + Возвращает: + - обновленную `RentalSession` со статусом `CANCELED` + + Ошибки: + - `ForbiddenAction` — пользователь не является владельцем сессии или сессия не в статусе `RESERVED` """ session = RentalSession.get(id=session_id, session=db.session) @@ -545,16 +663,25 @@ async def update_rental_session( session_id: int, update_data: RentalSessionPatch, user=Depends(UnionAuth(scopes=["rental.session.admin"])) ): """ - Updates the information of a rental session. + Обновляет данные сессии аренды по её идентификатору. + + Эндпоинт позволяет частично изменить поля сессии аренды, переданные в `update_data`. + Обновляются только те значения, которые были явно указаны в запросе. + + После обновления действие логируется как `UPDATE_SESSION`. - Scopes: `["rental.session.admin"]` + Скоупы: + - `rental.session.admin` - - **session_id**: The ID of the rental session to update. - - **update_data**: The data to update the session with. + Параметры: + - `session_id` — идентификатор сессии аренды + - `update_data` — набор полей для частичного обновления сессии - Returns the updated rental session. + Возвращает: + - обновленный объект `RentalSession` - Raises **ObjectNotFound** if the session with the specified ID is not found. + Ошибки: + - `ObjectNotFound` — сессия с указанным id не найдена """ session = RentalSession.get(id=session_id, session=db.session) if not session: diff --git a/rental_backend/routes/strike.py b/rental_backend/routes/strike.py index a0afc5d..99a4276 100644 --- a/rental_backend/routes/strike.py +++ b/rental_backend/routes/strike.py @@ -11,7 +11,6 @@ from rental_backend.schemas.models import StrikeGet, StrikePost from rental_backend.utils.action import ActionLogger - strike = APIRouter(prefix="/strike", tags=["Strike"]) @@ -20,15 +19,27 @@ async def create_strike( strike_info: StrikePost, user=Depends(UnionAuth(scopes=["rental.strike.create"], allow_none=False)) ) -> StrikeGet: """ - Creates a new strike. + Создает новый страйк. + + Перед созданием проверяется, существует ли сессия аренды с указанным `session_id`. + После успешного создания действие логируется как `CREATE_STRIKE`. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на создание страйков + - Сессия аренды с указанным `session_id` должна существовать - Scopes: `["rental.strike.create"]` + Скоупы: + - `rental.strike.create` - - **strike_info**: The data for the new strike. + Параметры: + - `strike_info` — данные нового страйка - Returns the created strike. + Возвращает: + - созданный объект `Strike` - If session does not exist returns ObjectNotFound. + Ошибки: + - `ObjectNotFound` — сессия аренды с указанным `session_id` не найдена """ sessions = db.session.query(RentalSession).filter(RentalSession.id == strike_info.session_id).one_or_none() if not sessions: @@ -49,11 +60,22 @@ async def create_strike( @strike.get("/user/{user_id}", response_model=list[StrikeGet]) async def get_user_strikes(user_id: int) -> list[StrikeGet]: """ - Retrieves a list of strikes for a specific user. + Возвращает список страйков пользователя по его идентификатору. + + Условия: + - отсутствуют (доступно без авторизации) + + Скоупы: + - отсутствуют - - **user_id**: The ID of the user. + Параметры: + - `user_id` — идентификатор пользователя - Returns a list of strikes. + Возвращает: + - список объектов `Strike` + + Ошибки: + - отсутствуют """ strikes = Strike.query(session=db.session).filter(Strike.user_id == user_id).all() return [StrikeGet.model_validate(strike) for strike in strikes] @@ -69,18 +91,31 @@ async def get_strikes( user=Depends(UnionAuth(scopes=["rental.strike.read"], allow_none=False)), ) -> list[StrikeGet]: """ - Retrieves a list of strikes with optional filtering. + Возвращает список страйков с возможностью фильтрации. + + Эндпоинт позволяет получить страйки и отфильтровать их по пользователю, + администратору, сессии аренды и диапазону дат создания. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на просмотр страйков + - При использовании фильтра по дате должны быть указаны оба параметра: `from_date` и `to_date` - Scopes: `["rental.strike.read"]` + Скоупы: + - `rental.strike.read` - - **admin_id**: Filter strikes by admin ID. - - **session_id**: Filter strikes by session ID. - - **from_date**: Filter strikes created after this date. - - **to_date**: Filter strikes created before this date. + Параметры: + - `user_id` — (необязательный) фильтр по идентификатору пользователя + - `admin_id` — (необязательный) фильтр по идентификатору администратора + - `session_id` — (необязательный) фильтр по идентификатору сессии аренды + - `from_date` — (необязательный) начало диапазона дат создания + - `to_date` — (необязательный) конец диапазона дат создания - Returns a list of strikes. + Возвращает: + - список объектов `Strike` - Raises **DateRangeError** if only one of `from_date` or `to_date` is provided. + Ошибки: + - `DateRangeError` — указан только один из параметров `from_date` или `to_date` """ if (from_date is None) != (to_date is None): raise DateRangeError() @@ -103,15 +138,27 @@ async def delete_strike( id: int, user=Depends(UnionAuth(scopes=["rental.strike.delete"], allow_none=False)) ) -> StatusResponseModel: """ - Deletes a strike by its ID. + Удаляет страйк по его идентификатору. + + Перед удалением проверяется, существует ли страйк с указанным `id`. + После успешного удаления действие логируется как `DELETE_STRIKE`. + + Условия: + - Пользователь должен быть аутентифицирован + - Пользователь должен иметь право на удаление страйков + - Страйк с указанным `id` должен существовать - Scopes: `["rental.strike.delete"]` + Скоупы: + - `rental.strike.delete` - - **id**: The ID of the strike to delete. + Параметры: + - `id` — идентификатор страйка - Returns a status response. + Возвращает: + - объект `StatusResponseModel` со статусом удаления - Raises **ObjectNotFound** if the strike with the specified ID is not found. + Ошибки: + - `ObjectNotFound` — страйк с указанным `id` не найден """ strike = Strike.get(id, session=db.session) if strike is None: