mirror of
https://github.com/chenasraf/nextcloud-pantry.git
synced 2026-05-17 17:28:01 +00:00
feat(checklist): trash mode toggle to view deleted items
This commit is contained in:
@@ -200,6 +200,34 @@ final class ChecklistController extends OCSController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List soft-deleted items in a checklist (trash)
|
||||
*
|
||||
* Returns items whose deleted_at is set, most recently deleted first.
|
||||
*
|
||||
* @param int $houseId House id.
|
||||
* @param int $listId List id.
|
||||
* @param int<1, 1000> $limit Maximum number of items to return.
|
||||
* @param int<0, max> $offset Number of items to skip.
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, list<PantryListItem>, array{}>
|
||||
*
|
||||
* 200: Deleted items returned
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/lists/{listId}/items/trash')]
|
||||
#[NoAdminRequired]
|
||||
public function indexDeletedItems(int $houseId, int $listId, int $limit = 200, int $offset = 0): DataResponse {
|
||||
return $this->runAction(function () use ($houseId, $listId, $limit, $offset): DataResponse {
|
||||
$this->auth->requireMember($houseId, $this->requireUid());
|
||||
$list = $this->lists->getList($listId);
|
||||
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||
$all = $this->lists->listDeletedItems($listId);
|
||||
$sliced = array_slice($all, max(0, $offset), max(0, $limit));
|
||||
$items = array_map(fn ($i) => $i->jsonSerialize(), $sliced);
|
||||
return new DataResponse($items);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to a list
|
||||
*
|
||||
@@ -401,6 +429,84 @@ final class ChecklistController extends OCSController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a soft-deleted item back into the active list
|
||||
*
|
||||
* @param int $houseId House id.
|
||||
* @param int $listId List id.
|
||||
* @param int $itemId Item id.
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, PantryListItem, array{}>
|
||||
*
|
||||
* 200: Item restored
|
||||
*/
|
||||
#[ApiRoute(verb: 'POST', url: '/api/houses/{houseId}/lists/{listId}/items/{itemId}/restore')]
|
||||
#[NoAdminRequired]
|
||||
public function restoreItem(int $houseId, int $listId, int $itemId): DataResponse {
|
||||
return $this->runAction(function () use ($houseId, $listId, $itemId): DataResponse {
|
||||
$this->auth->requireMember($houseId, $this->requireUid());
|
||||
$item = $this->lists->getItem($itemId, includeDeleted: true);
|
||||
$list = $this->lists->getList($item->getListId());
|
||||
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||
if ($item->getListId() !== $listId) {
|
||||
throw new NotFoundException('Item does not belong to this list');
|
||||
}
|
||||
$restored = $this->lists->restoreItem($itemId);
|
||||
return new DataResponse($restored->jsonSerialize());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently delete an item, bypassing the trash
|
||||
*
|
||||
* Works on both live items and items already in trash.
|
||||
*
|
||||
* @param int $houseId House id.
|
||||
* @param int $listId List id.
|
||||
* @param int $itemId Item id.
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, PantrySuccess, array{}>
|
||||
*
|
||||
* 200: Item permanently deleted
|
||||
*/
|
||||
#[ApiRoute(verb: 'DELETE', url: '/api/houses/{houseId}/lists/{listId}/items/{itemId}/permanent')]
|
||||
#[NoAdminRequired]
|
||||
public function permanentlyDeleteItem(int $houseId, int $listId, int $itemId): DataResponse {
|
||||
return $this->runAction(function () use ($houseId, $listId, $itemId): DataResponse {
|
||||
$this->auth->requireMember($houseId, $this->requireUid());
|
||||
$item = $this->lists->getItem($itemId, includeDeleted: true);
|
||||
$list = $this->lists->getList($item->getListId());
|
||||
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||
if ($item->getListId() !== $listId) {
|
||||
throw new NotFoundException('Item does not belong to this list');
|
||||
}
|
||||
$this->lists->permanentlyDeleteItem($itemId);
|
||||
return new DataResponse(['success' => true]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty a list's trash, permanently deleting every soft-deleted item
|
||||
*
|
||||
* @param int $houseId House id.
|
||||
* @param int $listId List id.
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, PantrySuccess, array{}>
|
||||
*
|
||||
* 200: Trash emptied
|
||||
*/
|
||||
#[ApiRoute(verb: 'DELETE', url: '/api/houses/{houseId}/lists/{listId}/items/trash')]
|
||||
#[NoAdminRequired]
|
||||
public function emptyTrash(int $houseId, int $listId): DataResponse {
|
||||
return $this->runAction(function () use ($houseId, $listId): DataResponse {
|
||||
$this->auth->requireMember($houseId, $this->requireUid());
|
||||
$list = $this->lists->getList($listId);
|
||||
$this->assertListInHouse($list->getHouseId(), $houseId);
|
||||
$this->lists->emptyTrash($listId);
|
||||
return new DataResponse(['success' => true]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch reorder items in a list
|
||||
*
|
||||
|
||||
@@ -135,10 +135,37 @@ class ChecklistItemMapper extends QBMapper {
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find soft-deleted items in a list, most recently deleted first.
|
||||
*
|
||||
* @return ChecklistItem[]
|
||||
*/
|
||||
public function findDeletedByList(int $listId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->isNotNull('deleted_at'))
|
||||
->orderBy('deleted_at', 'DESC');
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function deleteByList(int $listId): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete($this->getTableName())
|
||||
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-delete every soft-deleted item in the list.
|
||||
*/
|
||||
public function emptyTrashForList(int $listId): void {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete($this->getTableName())
|
||||
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->isNotNull('deleted_at'));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +106,18 @@ class ChecklistService {
|
||||
return $this->itemMapper->findByList($listId, $sortBy);
|
||||
}
|
||||
|
||||
public function getItem(int $itemId): ChecklistItem {
|
||||
/**
|
||||
* List soft-deleted items for a list. Most recently deleted first.
|
||||
*
|
||||
* @return ChecklistItem[]
|
||||
*/
|
||||
public function listDeletedItems(int $listId): array {
|
||||
return $this->itemMapper->findDeletedByList($listId);
|
||||
}
|
||||
|
||||
public function getItem(int $itemId, bool $includeDeleted = false): ChecklistItem {
|
||||
try {
|
||||
return $this->itemMapper->findById($itemId);
|
||||
return $this->itemMapper->findById($itemId, $includeDeleted);
|
||||
} catch (DoesNotExistException) {
|
||||
throw new NotFoundException('Item not found');
|
||||
}
|
||||
@@ -360,6 +369,33 @@ class ChecklistService {
|
||||
$this->itemMapper->update($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently remove an item, regardless of whether it is currently in
|
||||
* trash. Bypasses the soft-delete row and erases it from the table.
|
||||
*/
|
||||
public function permanentlyDeleteItem(int $itemId): void {
|
||||
$item = $this->getItem($itemId, includeDeleted: true);
|
||||
$this->itemMapper->delete($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a soft-deleted item by clearing its deleted_at marker.
|
||||
*/
|
||||
public function restoreItem(int $itemId): ChecklistItem {
|
||||
$item = $this->getItem($itemId, includeDeleted: true);
|
||||
$item->setDeletedAt(null);
|
||||
$item->setUpdatedAt(time());
|
||||
$this->itemMapper->update($item);
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-delete every soft-deleted item in the list.
|
||||
*/
|
||||
public function emptyTrash(int $listId): void {
|
||||
$this->itemMapper->emptyTrashForList($listId);
|
||||
}
|
||||
|
||||
private function strOrNull(mixed $v): ?string {
|
||||
if (!is_string($v)) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user