Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rewrite file list #3746

Merged
merged 48 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
70d463d
feat: rewrite file list
vitormattos Sep 20, 2024
9197f49
chore: start to implement FileEntry component
vitormattos Sep 23, 2024
ae251c8
fix: linter
vitormattos Sep 24, 2024
1a56cff
feat: add FileEntryName component
vitormattos Sep 24, 2024
34cb4c2
chore: add component EntryAction
vitormattos Sep 24, 2024
1a1665e
feat: trigger opened menu on right click
vitormattos Sep 25, 2024
5b951d2
chore: make FileEntry at table mode to work
vitormattos Sep 25, 2024
d351026
chore: remove checkbox
vitormattos Sep 25, 2024
abf9f18
fix: header sorting arrow icon
v-bianchi Sep 25, 2024
5e613a2
feat: add status dot to file entry component
v-bianchi Sep 25, 2024
3d8d93f
fix: linter
vitormattos Sep 25, 2024
3c325bf
feat: open file details side bar
v-bianchi Sep 26, 2024
1102bd4
feat: implement file filtering logic WIP
v-bianchi Sep 26, 2024
bbbc826
feat: filter files according to status and modified values
v-bianchi Sep 30, 2024
be783aa
chore: update OpenAPI documentation
v-bianchi Sep 30, 2024
1026c86
feat: implement file sorting
v-bianchi Sep 30, 2024
d3aedd7
chore: implement buttons to reques to sign
vitormattos Oct 14, 2024
405e781
chore: move methods to store
vitormattos Oct 14, 2024
c14e5b0
chore: make sign menu to work
vitormattos Oct 19, 2024
5e83492
chore: implement validate
vitormattos Oct 19, 2024
9a9ff67
chore: start to implement delete
vitormattos Oct 21, 2024
d34029a
fix: prevent to be funcion when isn't
vitormattos Oct 24, 2024
b01976e
fix: lint
vitormattos Oct 24, 2024
0d44f7a
chore: confirm before delete
vitormattos Oct 24, 2024
554c18a
chore: indent
vitormattos Oct 24, 2024
f5b5b57
chore: make reactive the conditional to display actions
vitormattos Oct 24, 2024
317fe81
fix: sing from action
vitormattos Oct 24, 2024
37b1a05
chore: only delete from backend when the file exists at LibreSign side
vitormattos Oct 24, 2024
ecf7688
fix: way to get signer uuid
vitormattos Oct 24, 2024
3305ccb
feat: implement order
vitormattos Oct 24, 2024
c244c75
feat: implement lazy loading of files with pagination
v-bianchi Oct 28, 2024
503856f
feat: integrate file list filters and pagination query params WIP
v-bianchi Oct 31, 2024
37e16f1
fix: prevent infinity loop when use IntersectionObserver
vitormattos Oct 31, 2024
8fc7b64
fix: linter
vitormattos Oct 31, 2024
f53d524
chore: remove timeline
vitormattos Oct 31, 2024
ddac8cf
chore: phpcs fix
vitormattos Oct 31, 2024
7b20f2c
fix: SPDX
vitormattos Oct 31, 2024
a070a09
fix: prevent add duplicated element to array
vitormattos Oct 31, 2024
8a013ce
fix: copy pdfworker to LibreSign
vitormattos Oct 31, 2024
96e5f06
feat: integrate files filtering and pagination, fix IntersectionObserver
v-bianchi Oct 31, 2024
5284d78
fix: integration tests
vitormattos Oct 31, 2024
3bb3e3e
refactor: make more readdable
vitormattos Oct 31, 2024
b1ae210
refactor: make more readdable
vitormattos Oct 31, 2024
a417d3d
fix: query string order
vitormattos Oct 31, 2024
430b947
chore: remove delete from filter
vitormattos Oct 31, 2024
940f018
fix: order url parameters of pagination
vitormattos Nov 1, 2024
454b8d3
feat: make possible delete file too
vitormattos Nov 5, 2024
f46ee43
feat: Save file to LibeSign every when a new file is uploaded
vitormattos Nov 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions lib/Controller/FileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use OCA\Files_Sharing\SharedStorage;
use OCA\Libresign\AppInfo\Application;
use OCA\Libresign\Db\File as FileEntity;
use OCA\Libresign\Db\SignRequestMapper;
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Helper\JSActions;
Expand All @@ -19,6 +20,7 @@
use OCA\Libresign\Service\AccountService;
use OCA\Libresign\Service\FileService;
use OCA\Libresign\Service\IdentifyMethodService;
use OCA\Libresign\Service\RequestSignatureService;
use OCA\Libresign\Service\SessionService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
Expand Down Expand Up @@ -59,6 +61,7 @@ public function __construct(
private SessionService $sessionService,
private SignRequestMapper $signRequestMapper,
private IdentifyMethodService $identifyMethodService,
private RequestSignatureService $requestSignatureService,
private AccountService $accountService,
private IRootFolder $root,
private IPreview $preview,
Expand Down Expand Up @@ -192,9 +195,13 @@ public function validate(?string $type = null, $identifier = null): DataResponse
*
* @param string|null $signer_uuid Signer UUID
* @param string|null $nodeId The nodeId (also called fileId). Is the id of a file at Nextcloud
* @param int|null $status Status could be one of 0 = draft, 1 = able to sign, 2 = partial signed, 3 = signed, 4 = deleted.
* @param list<int>|null $status Status could be none or many of 0 = draft, 1 = able to sign, 2 = partial signed, 3 = signed, 4 = deleted.
* @param int|null $page the number of page to return
* @param int|null $length Total of elements to return
* @param int|null $start Start date of signature request (UNIX timestamp)
* @param int|null $end End date of signature request (UNIX timestamp)
* @param string|null $sortBy Name of the column to sort by
* @param string|null $sortDirection Ascending or descending order
* @return DataResponse<Http::STATUS_OK, array{pagination: LibresignPagination, data: ?LibresignFile[]}, array{}>
*
* 200: OK
Expand All @@ -207,16 +214,26 @@ public function list(
?int $length = null,
?string $signer_uuid = null,
?string $nodeId = null,
?int $status = null,
?array $status = null,
?int $start = null,
?int $end = null,
?string $sortBy = null,
?string $sortDirection = null,
): DataResponse {
$filter = array_filter([
'signer_uuid' => $signer_uuid,
'nodeId' => $nodeId,
'status' => $status,
'start' => $start,
'end' => $end,
], static function ($var) { return $var !== null; });
$sort = [
'sortBy' => $sortBy,
'sortDirection' => $sortDirection,
];
$return = $this->fileService
->setMe($this->userSession->getUser())
->listAssociatedFilesOfSignFlow($page, $length, $filter);
->listAssociatedFilesOfSignFlow($page, $length, $filter, $sort);
return new DataResponse($return, Http::STATUS_OK);
}

Expand Down Expand Up @@ -356,6 +373,15 @@ public function save(array $file, string $name = '', array $settings = []): Data
'file' => $file,
'settings' => $settings
]);
$data = [
'file' => [
'fileNode' => $node,
],
'name' => $name,
'userManager' => $this->userSession->getUser(),
'status' => FileEntity::STATUS_DRAFT,
];
$file = $this->requestSignatureService->save($data);

return new DataResponse(
[
Expand All @@ -377,4 +403,46 @@ public function save(array $file, string $name = '', array $settings = []): Data
);
}
}

/**
* Delete File
*
* This will delete the file and all data
*
* @param integer $fileId Node id of a Nextcloud file
* @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: string[]}, array{}>
*
* 200: OK
* 401: Failed
* 422: Failed
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[RequireManager]
#[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/file/file_id/{fileId}', requirements: ['apiVersion' => '(v1)'])]
public function deleteAllRequestSignatureUsingFileId(int $fileId): DataResponse {
try {
$data = [
'userManager' => $this->userSession->getUser(),
'file' => [
'fileId' => $fileId
]
];
$this->validateHelper->validateExistingFile($data);
$this->fileService->delete($fileId);
} catch (\Throwable $th) {
return new DataResponse(
[
'message' => $th->getMessage(),
],
Http::STATUS_UNAUTHORIZED
);
}
return new DataResponse(
[
'message' => $this->l10n->t('Success')
],
Http::STATUS_OK
);
}
}
2 changes: 1 addition & 1 deletion lib/Db/AccountFileMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private function getUserAccountFile(array $filter = []): Pagination {
$qb->setMaxResults($filter['length']);
}

$pagination = new Pagination($qb);
$pagination = new Pagination($qb, $this->urlGenerator);
return $pagination;
}

Expand Down
29 changes: 25 additions & 4 deletions lib/Db/SignRequestMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,12 @@ public function getFilesAssociatedFilesWithMeFormatted(
array $filter,
?int $page = null,
?int $length = null,
?array $sort = [],
): array {
$filter['email'] = $user->getEMailAddress();
$filter['length'] = $length;
$filter['page'] = $page;
$pagination = $this->getFilesAssociatedFilesWithMeStmt($user->getUID(), $filter);
$pagination = $this->getFilesAssociatedFilesWithMeStmt($user->getUID(), $filter, $sort);
$pagination->setMaxPerPage($length);
$pagination->setCurrentPage($page);
$currentPageResults = $pagination->getCurrentPageResults();
Expand Down Expand Up @@ -472,7 +473,17 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, ?arra
}
if (!empty($filter['status'])) {
$qb->andWhere(
$qb->expr()->eq('f.status', $qb->createNamedParameter($filter['status'], IQueryBuilder::PARAM_INT))
$qb->expr()->in('f.status', $qb->createNamedParameter($filter['status'], IQueryBuilder::PARAM_INT_ARRAY))
);
}
if (!empty($filter['start'])) {
$qb->andWhere(
$qb->expr()->gte('f.created_at', $qb->createNamedParameter($filter['start'], IQueryBuilder::PARAM_INT))
);
}
if (!empty($filter['end'])) {
$qb->andWhere(
$qb->expr()->lte('f.created_at', $qb->createNamedParameter($filter['end'], IQueryBuilder::PARAM_INT))
);
}
if (isset($filter['length']) && isset($filter['page'])) {
Expand All @@ -483,7 +494,11 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, ?arra
return $qb;
}

private function getFilesAssociatedFilesWithMeStmt(string $userId, ?array $filter = []): Pagination {
private function getFilesAssociatedFilesWithMeStmt(
string $userId,
?array $filter = [],
?array $sort = [],
): Pagination {
$qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($userId, $filter);
$qb->select(
'f.id',
Expand All @@ -494,6 +509,12 @@ private function getFilesAssociatedFilesWithMeStmt(string $userId, ?array $filte
'f.status',
'f.metadata',
);
if (!empty($sort) && in_array($sort['sortBy'], ['name', 'status', 'created_at'])) {
$qb->orderBy(
$qb->func()->lower('f.' . $sort['sortBy']),
$sort['sortDirection'] == 'asc' ? 'asc' : 'desc'
);
}
$qb->selectAlias('f.created_at', 'request_date');

$countQueryBuilderModifier = function (IQueryBuilder $qb): int {
Expand All @@ -505,7 +526,7 @@ private function getFilesAssociatedFilesWithMeStmt(string $userId, ?array $filte
return (int)$qb->executeQuery()->fetchOne();
};

$pagination = new Pagination($qb);
$pagination = new Pagination($qb, $this->urlGenerator);
return $pagination;
}

Expand Down
71 changes: 52 additions & 19 deletions lib/Helper/Pagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@

use OCA\Libresign\Db\PagerFantaQueryAdapter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IURLGenerator;
use Pagerfanta\Pagerfanta;

class Pagination extends Pagerfanta {
/** @var string */
private $rootPath;
private string $routeName;
public function __construct(
IQueryBuilder $queryBuilder,
private IURLGenerator $urlGenerator,
) {
$adapter = new PagerFantaQueryAdapter($queryBuilder);
parent::__construct($adapter);
Expand All @@ -25,27 +26,59 @@ public function __construct(
/**
* @return static
*/
public function setRootPath(string $rootPath = ''): self {
$this->rootPath = $rootPath;
public function setRouteName(string $routeName = ''): self {
$this->routeName = $routeName;
return $this;
}

public function getPagination(?int $page, ?int $length): array {
public function getPagination(int $page, int $length, array $filter = []): array {
$this->setMaxPerPage($length);
$pagination['total'] = $this->count();
if ($pagination['total'] > $length) {
$pagination['current'] = $this->rootPath . '?page=' . $page . '&length=' . $length;
$pagination['next'] = $this->hasNextPage() ? $this->rootPath . '?page=' . $this->getNextPage() . '&length=' . $length : null;
$pagination['prev'] = $this->hasPreviousPage() ? $this->rootPath . '?page=' . $this->getPreviousPage() . '&length=' . $length : null;
$pagination['last'] = $this->hasNextPage() ? $this->rootPath . '?page=' . $this->getNbPages() . '&length=' . $length : null;
$pagination['first'] = $this->hasPreviousPage() ? $this->rootPath . '?page=1&length=' . $length : null;
} else {
$pagination['current'] = null;
$pagination['next'] = null;
$pagination['prev'] = null;
$pagination['last'] = null;
$pagination['first'] = null;
$total = $this->count();
if ($total > $length) {
return [
'total' => $total,
'current' => $this->linkToRoute(true, $page, $length, $filter),
'next' => $this->linkToRoute($this->hasNextPage(), 'getNextPage', $length, $filter),
'prev' => $this->linkToRoute($this->hasPreviousPage(), 'getPreviousPage', $length, $filter),
'last' => $this->linkToRoute($this->hasNextPage(), 'getNbPages', $length, $filter),
'first' => $this->linkToRoute($this->hasPreviousPage(), 1, $length, $filter),
];
}
return $pagination;
return [
'total' => $total,
'current' => null,
'next' => null,
'prev' => null,
'last' => null,
'first' => null,
];
}

private function linkToRoute(bool $condition, int|string $page, int $length, array $filter): ?string {
if (!$condition) {
return null;
}
if (is_string($page)) {
$page = $this->$page();
}
$url = $this->urlGenerator->linkToRouteAbsolute(
$this->routeName,
array_merge(['page' => $page, 'length' => $length, 'apiVersion' => 'v1'], $filter)
);
$url = $this->sortParameters($url);
return $url;
}

/**
* This is necessary to fix problem at integration tests because the method linkToRoute change the order of parameters
*/
private function sortParameters(?string $url): ?string {
if (!$url) {
return $url;
}
parse_str(parse_url($url, PHP_URL_QUERY), $query);
ksort($query);
$url = strtok($url, '?') . '?' . http_build_query($query);
return $url;
}
}
4 changes: 2 additions & 2 deletions lib/Service/AccountFileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public function accountFileList(array $filter, ?int $page = null, ?int $length =
$page = $page ?? 1;
$length = $length ?? (int)$this->appConfig->getAppValue('length_of_page', '100');
$data = $this->accountFileMapper->accountFileList($filter, $page, $length);
$data['pagination']->setRootPath('/file/list');
$data['pagination']->setRouteName('ocs.libresign.File.list');
return [
'data' => $data['data'],
'pagination' => $data['pagination']->getPagination($page, $length)
'pagination' => $data['pagination']->getPagination($page, $length, $filter)
];
}
}
28 changes: 25 additions & 3 deletions lib/Service/FileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,12 @@ public function setFileByPath(string $path): self {
*
* @psalm-return array{data: array, pagination: array}
*/
public function listAssociatedFilesOfSignFlow($page = null, $length = null, array $filter = []): array {
public function listAssociatedFilesOfSignFlow(
$page = null,
$length = null,
array $filter = [],
array $sort = [],
): array {
$page = $page ?? 1;
$length = $length ?? (int)$this->appConfig->getAppValue('length_of_page', '100');

Expand All @@ -441,17 +446,18 @@ public function listAssociatedFilesOfSignFlow($page = null, $length = null, arra
$filter,
$page,
$length,
$sort,
);

$signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id'));
$identifyMethods = $this->signRequestMapper->getIdentifyMethodsFromSigners($signers);
$visibleElements = $this->signRequestMapper->getVisibleElementsFromSigners($signers);
$return['data'] = $this->associateAllAndFormat($this->me, $return['data'], $signers, $identifyMethods, $visibleElements);

$return['pagination']->setRootPath('/file/list');
$return['pagination']->setRouteName('ocs.libresign.File.list');
return [
'data' => $return['data'],
'pagination' => $return['pagination']->getPagination($page, $length)
'pagination' => $return['pagination']->getPagination($page, $length, $filter)
];
}

Expand Down Expand Up @@ -585,4 +591,20 @@ public function getMyLibresignFile(int $nodeId): File {
],
);
}

public function delete(int $fileId): void {
$file = $this->fileMapper->getByFileId($fileId);
$this->fileElementService->deleteVisibleElements($file->getId());
$list = $this->signRequestMapper->getByFileId($file->getId());
foreach ($list as $signRequest) {
$this->signRequestMapper->delete($signRequest);
}
$this->fileMapper->delete($file);
if ($file->getSignedNodeId()) {
$signedNextcloudFile = $this->folderService->getFileById($file->getSignedNodeId());
$signedNextcloudFile->delete();
}
$nextcloudFile = $this->folderService->getFileById($fileId);
$nextcloudFile->delete();
}
}
Loading
Loading