Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ActiveSigningsService
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
6 / 6
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getActiveSignings
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 resolveActiveSigner
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 findActiveSignRequest
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 findPreferredIdentifyMethod
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 resolveActiveSigningUpdatedAt
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service;
10
11use OCA\Libresign\Db\File as FileEntity;
12use OCA\Libresign\Db\FileMapper;
13use OCA\Libresign\Db\IdentifyMethod as IdentifyMethodEntity;
14use OCA\Libresign\Db\IdentifyMethodMapper;
15use OCA\Libresign\Db\SignRequest as SignRequestEntity;
16use OCA\Libresign\Db\SignRequestMapper;
17use OCA\Libresign\Enum\FileStatus;
18use OCA\Libresign\Enum\SignRequestStatus;
19
20class ActiveSigningsService {
21    private const EMAIL_IDENTIFY_METHOD = 'email';
22
23    public function __construct(
24        private FileMapper $fileMapper,
25        private SignRequestMapper $signRequestMapper,
26        private IdentifyMethodMapper $identifyMethodMapper,
27    ) {
28    }
29
30    /**
31     * @return list<array{
32     *     id: int,
33     *     uuid: string,
34     *     name: string,
35     *     signerEmail: string,
36     *     signerDisplayName: string,
37     *     updatedAt: int,
38     * }>
39     */
40    public function getActiveSignings(): array {
41        $activeSignings = $this->fileMapper->findByStatus(FileStatus::SIGNING_IN_PROGRESS->value);
42
43        $result = [];
44        foreach ($activeSignings as $file) {
45            $activeSigner = $this->resolveActiveSigner($file);
46            $result[] = [
47                'id' => $file->getId(),
48                'uuid' => $file->getUuid(),
49                'name' => $file->getName(),
50                'signerEmail' => $activeSigner['email'],
51                'signerDisplayName' => $activeSigner['displayName'],
52                'updatedAt' => $this->resolveActiveSigningUpdatedAt($file),
53            ];
54        }
55
56        return $result;
57    }
58
59    /**
60     * @return array{email: string, displayName: string}
61     */
62    private function resolveActiveSigner(FileEntity $file): array {
63        $signRequest = $this->findActiveSignRequest($this->signRequestMapper->getByFileId($file->getId()));
64        if (!$signRequest instanceof SignRequestEntity) {
65            return [
66                'email' => '',
67                'displayName' => '',
68            ];
69        }
70
71        $identifiedMethod = $this->findPreferredIdentifyMethod(
72            $this->identifyMethodMapper->getIdentifyMethodsFromSignRequestId($signRequest->getId())
73        );
74
75        $signerEmail = '';
76        if ($identifiedMethod instanceof IdentifyMethodEntity
77            && $identifiedMethod->getIdentifierKey() === self::EMAIL_IDENTIFY_METHOD
78        ) {
79            $signerEmail = $identifiedMethod->getIdentifierValue();
80        }
81
82        return [
83            'email' => $signerEmail,
84            'displayName' => $signRequest->getDisplayName(),
85        ];
86    }
87
88    /**
89     * @param list<SignRequestEntity> $signRequests
90     */
91    private function findActiveSignRequest(array $signRequests): ?SignRequestEntity {
92        foreach ($signRequests as $signRequest) {
93            if ($signRequest->getStatusEnum() === SignRequestStatus::ABLE_TO_SIGN) {
94                return $signRequest;
95            }
96        }
97
98        foreach ($signRequests as $signRequest) {
99            if ($signRequest->getSigned() === null) {
100                return $signRequest;
101            }
102        }
103
104        return $signRequests[0] ?? null;
105    }
106
107    /**
108     * @param list<IdentifyMethodEntity> $identifyMethods
109     */
110    private function findPreferredIdentifyMethod(array $identifyMethods): ?IdentifyMethodEntity {
111        $firstMethod = null;
112
113        foreach ($identifyMethods as $identifyMethod) {
114            $firstMethod ??= $identifyMethod;
115
116            if ($identifyMethod->getIdentifiedAtDate() !== null) {
117                return $identifyMethod;
118            }
119        }
120
121        return $firstMethod;
122    }
123
124    private function resolveActiveSigningUpdatedAt(FileEntity $file): int {
125        $metadata = $file->getMetadata();
126        if (is_array($metadata) && isset($metadata['status_changed_at']) && is_string($metadata['status_changed_at'])) {
127            $statusChangedAt = date_create_immutable($metadata['status_changed_at']);
128            if ($statusChangedAt instanceof \DateTimeImmutable) {
129                return $statusChangedAt->getTimestamp();
130            }
131        }
132
133        return $file->getCreatedAt()?->getTimestamp() ?? 0;
134    }
135}