• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 21017989272

15 Jan 2026 02:53AM UTC coverage: 44.43%. First build
21017989272

Pull #6436

github

web-flow
Merge 6ef45535a into db0316516
Pull Request #6436: feat: async parallel signing

415 of 962 new or added lines in 46 files covered. (43.14%)

7036 of 15836 relevant lines covered (44.43%)

4.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.13
/lib/Service/SignJobCoordinator.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service;
10

11
use DateTime;
12
use DateTimeInterface;
13
use OCA\Libresign\Db\File as FileEntity;
14
use OCA\Libresign\Db\FileMapper;
15
use OCA\Libresign\Db\SignRequest as SignRequestEntity;
16
use OCA\Libresign\Db\SignRequestMapper;
17
use OCA\Libresign\Enum\FileStatus;
18
use OCP\IUser;
19
use OCP\IUserManager;
20
use OCP\Security\ICredentialsManager;
21
use Psr\Log\LoggerInterface;
22

23
class SignJobCoordinator {
24
        public function __construct(
25
                private FileMapper $fileMapper,
26
                private SignRequestMapper $signRequestMapper,
27
                private SignFileService $signFileService,
28
                private FolderService $folderService,
29
                private IUserManager $userManager,
30
                private ICredentialsManager $credentialsManager,
31
                private LoggerInterface $logger,
32
        ) {
33
        }
2✔
34

35
        public function runSignFile(array $argument): void {
36
                try {
NEW
37
                        if (empty($argument)) {
×
NEW
38
                                throw new \InvalidArgumentException('SignFileJob: Cannot proceed with empty arguments');
×
39
                        }
40

NEW
41
                        [$fileId, $signRequestId] = $this->requireIds($argument, 'SignFileJob');
×
NEW
42
                        [$file, $signRequest] = $this->loadEntities($fileId, $signRequestId);
×
NEW
43
                        $user = $this->resolveUser($argument['userId'] ?? null, null, null);
×
NEW
44
                        if ($user) {
×
NEW
45
                                $this->folderService->setUserId($user->getUID());
×
46
                        }
47

NEW
48
                        $credentials = $this->retrieveCredentials($user, $argument['credentialsId'] ?? null);
×
NEW
49
                        $this->hydrateSignService($argument, $credentials, $file, $signRequest, $user);
×
50

NEW
51
                        $this->signFileService->sign();
×
NEW
52
                } catch (\Throwable $e) {
×
NEW
53
                        $this->logger->error('SignFileJob failed: {error}', [
×
NEW
54
                                'error' => $e->getMessage(),
×
NEW
55
                                'exception' => $e,
×
NEW
56
                        ]);
×
57
                } finally {
NEW
58
                        $this->deleteCredentials($argument['userId'] ?? '', $argument['credentialsId'] ?? null);
×
59
                }
60
        }
61

62
        public function runSignSingleFile(array $argument): void {
63
                $fileId = $argument['fileId'] ?? null;
2✔
64
                $signRequestId = $argument['signRequestId'] ?? null;
2✔
65
                $effectiveUserId = $argument['userId'] ?? null;
2✔
66
                $file = null;
2✔
67
                $signRequest = null;
2✔
68

69
                try {
70
                        if (empty($argument)) {
2✔
NEW
71
                                throw new \InvalidArgumentException('SignSingleFileJob: Cannot proceed with empty arguments');
×
72
                        }
73

74
                        [$fileId, $signRequestId] = $this->requireIds($argument, 'SignSingleFileJob');
2✔
75
                        [$file, $signRequest] = $this->loadEntities($fileId, $signRequestId);
2✔
76

77

78
                        $effectiveUserId = $effectiveUserId
2✔
79
                                ?? $file->getUserId()
2✔
NEW
80
                                ?? ($signRequest->getUserId() ?? null);
×
81
                        $user = $this->resolveUser($effectiveUserId);
2✔
82
                        if ($user) {
2✔
83
                                $this->folderService->setUserId($user->getUID());
2✔
84
                        }
85

86
                        $credentials = $this->retrieveCredentials($user, $argument['credentialsId'] ?? null);
2✔
87
                        $this->hydrateSignService($argument, $credentials, $file, $signRequest, $user);
2✔
88

89
                        $this->markInProgress($file);
2✔
90

91
                        $this->signFileService->signSingleFile($file, $signRequest);
2✔
92
                } catch (\Throwable $e) {
1✔
93
                        $contextFileId = $fileId ?? ($argument['fileId'] ?? 'unknown');
1✔
94
                        $this->logger->error('SignSingleFileJob failed for file {fileId}: {error}', [
1✔
95
                                'fileId' => $contextFileId,
1✔
96
                                'error' => $e->getMessage(),
1✔
97
                                'exception' => $e,
1✔
98
                        ]);
1✔
99
                } finally {
100
                        $this->deleteCredentials($argument['userId'] ?? ($effectiveUserId ?? ''), $argument['credentialsId'] ?? null);
2✔
101
                }
102
        }
103

104
        /**
105
         * @return array{0:int,1:int}
106
         */
107
        private function requireIds(array $argument, string $jobName): array {
108
                $fileId = $argument['fileId'] ?? null;
2✔
109
                $signRequestId = $argument['signRequestId'] ?? null;
2✔
110
                if ($fileId === null || $signRequestId === null) {
2✔
NEW
111
                        $this->logger->error($jobName . ': Background job missing required arguments', [
×
NEW
112
                                'jobName' => $jobName,
×
NEW
113
                                'hasFileId' => $fileId !== null,
×
NEW
114
                                'hasSignRequestId' => $signRequestId !== null,
×
NEW
115
                                'argumentKeys' => array_keys($argument),
×
NEW
116
                                'argumentCount' => count($argument),
×
NEW
117
                        ]);
×
NEW
118
                        throw new \InvalidArgumentException($jobName . ': Missing fileId or signRequestId');
×
119
                }
120
                return [$fileId, $signRequestId];
2✔
121
        }
122

123
        /**
124
         * @return array{0:FileEntity,1:SignRequestEntity}
125
         */
126
        private function loadEntities(int $fileId, int $signRequestId): array {
127
                $this->fileMapper->flushCache($fileId);
2✔
128
                $file = $this->fileMapper->getById($fileId);
2✔
129
                $this->signRequestMapper->flushCache($signRequestId);
2✔
130
                $signRequest = $this->signRequestMapper->getById($signRequestId);
2✔
131
                return [$file, $signRequest];
2✔
132
        }
133

134
        private function resolveUser(?string $userId): ?IUser {
135
                if (empty($userId)) {
2✔
NEW
136
                        return null;
×
137
                }
138
                return $this->userManager->get($userId);
2✔
139
        }
140

141
        private function retrieveCredentials(?IUser $user, ?string $credentialsId): ?array {
142
                if (empty($credentialsId) || $user === null) {
2✔
NEW
143
                        return null;
×
144
                }
145
                return $this->credentialsManager->retrieve($user->getUID(), $credentialsId);
2✔
146
        }
147

148
        private function hydrateSignService(array $argument, ?array $credentials, FileEntity $file, SignRequestEntity $signRequest, ?IUser $user): void {
149
                if ($credentials && !empty($credentials['signWithoutPassword'])) {
2✔
NEW
150
                        $this->signFileService->setSignWithoutPassword(true);
×
151
                } elseif ($credentials && !empty($credentials['password'])) {
2✔
152
                        $this->signFileService->setPassword($credentials['password']);
2✔
153
                }
154

155
                if (!empty($argument['userUniqueIdentifier'])) {
2✔
NEW
156
                        $this->signFileService->setUserUniqueIdentifier($argument['userUniqueIdentifier']);
×
157
                }
158

159
                if (!empty($argument['friendlyName'])) {
2✔
NEW
160
                        $this->signFileService->setFriendlyName($argument['friendlyName']);
×
161
                }
162

163
                $this->signFileService
2✔
164
                        ->setLibreSignFile($file)
2✔
165
                        ->setSignRequest($signRequest)
2✔
166
                        ->setCurrentUser($user)
2✔
167
                        ->storeUserMetadata($argument['metadata'] ?? [])
2✔
168
                        ->setVisibleElements($argument['visibleElements'] ?? []);
2✔
169
        }
170

171
        private function markInProgress(FileEntity $file): void {
172
                $statusBefore = $file->getStatus();
2✔
173
                if ($statusBefore === FileStatus::SIGNING_IN_PROGRESS->value) {
2✔
NEW
174
                        return;
×
175
                }
176

177
                $file->setStatusEnum(FileStatus::SIGNING_IN_PROGRESS);
2✔
178
                $meta = $file->getMetadata() ?? [];
2✔
179
                $meta['status_changed_at'] = (new DateTime())->format(DateTimeInterface::ATOM);
2✔
180
                $file->setMetadata($meta);
2✔
181
                $this->fileMapper->update($file);
2✔
182
        }
183

184
        private function deleteCredentials(string $userId, ?string $credentialsId): void {
185
                if (empty($credentialsId)) {
2✔
NEW
186
                        return;
×
187
                }
188
                $this->credentialsManager->delete($userId, $credentialsId);
2✔
189
        }
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc