Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 105 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
ImportMembersEndpoint | |
0.00% |
0 / 105 |
|
0.00% |
0 / 3 |
462 | |
0.00% |
0 / 1 |
handle | |
0.00% |
0 / 89 |
|
0.00% |
0 / 1 |
240 | |||
getCsvContent | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getUserData | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace Olz\Apps\Members\Endpoints; |
4 | |
5 | use Olz\Api\OlzTypedEndpoint; |
6 | use Olz\Apps\Members\Utils\MembersUtils; |
7 | use Olz\Entity\Members\Member; |
8 | use Olz\Entity\Users\User; |
9 | use PhpTypeScriptApi\HttpError; |
10 | |
11 | /** |
12 | * @phpstan-type OlzMemberInfo array{ |
13 | * ident: non-empty-string, |
14 | * action: 'CREATE'|'UPDATE'|'DELETE'|'KEEP', |
15 | * username?: ?non-empty-string, |
16 | * matchingUsername?: ?non-empty-string, |
17 | * user?: ?array{ |
18 | * id: int, |
19 | * firstName: non-empty-string, |
20 | * lastName: non-empty-string, |
21 | * }, |
22 | * updates: array<non-empty-string, array{old: string, new: string}>, |
23 | * } |
24 | * |
25 | * @extends OlzTypedEndpoint< |
26 | * array{csvFileId: non-empty-string}, |
27 | * array{status: 'OK'|'ERROR', members: array<OlzMemberInfo>} |
28 | * > |
29 | */ |
30 | class ImportMembersEndpoint extends OlzTypedEndpoint { |
31 | protected function handle(mixed $input): mixed { |
32 | if (!$this->authUtils()->hasPermission('vorstand')) { |
33 | throw new HttpError(403, "Kein Zugriff!"); |
34 | } |
35 | |
36 | $user = $this->authUtils()->getCurrentUser(); |
37 | $this->log()->info("Members import by {$user?->getUsername()}."); |
38 | |
39 | $member_info_by_ident = []; |
40 | $member_utils = new MembersUtils(); |
41 | $csv_content = $this->getCsvContent($input['csvFileId']); |
42 | $members = $member_utils->parseCsv($csv_content); |
43 | $member_repo = $this->entityManager()->getRepository(Member::class); |
44 | $user_repo = $this->entityManager()->getRepository(User::class); |
45 | |
46 | $existing_member_is_deleted = []; |
47 | foreach ($member_repo->getAllIdents() as $existing_member_ident) { |
48 | // Assume deleted unless set to false later... |
49 | $existing_member_is_deleted[$existing_member_ident] = true; |
50 | } |
51 | foreach ($members as $member) { |
52 | $member_ident = $member_utils->getMemberIdent($member); |
53 | $member_username = $member_utils->getMemberUsername($member); |
54 | $enc_member = json_encode($member); |
55 | $this->generalUtils()->checkNotFalse($enc_member, "JSON encode failed"); |
56 | if (!$member_ident) { |
57 | $this->log()->warning("Member has no ident: {$enc_member}"); |
58 | continue; |
59 | } |
60 | $existing_member_is_deleted[$member_ident] = false; |
61 | $user = $member_username ? ( |
62 | $user_repo->findOneBy(['username' => $member_username]) |
63 | ?? $user_repo->findOneBy(['old_username' => $member_username]) |
64 | ) : null; |
65 | $matching_user = $user_repo->findUserFuzzilyByName( |
66 | trim($member_utils->getMemberFirstName($member) ?? ''), |
67 | trim($member_utils->getMemberLastName($member) ?? ''), |
68 | ); |
69 | $base_info = [ |
70 | 'username' => $member_username, |
71 | 'matchingUsername' => $matching_user?->getUsername(), |
72 | 'user' => $this->getUserData($user), |
73 | ]; |
74 | $entity = $member_repo->findOneBy(['ident' => $member_ident]); |
75 | if (!$entity) { |
76 | $member_info_by_ident[$member_ident] = [...$base_info, 'action' => 'CREATE']; |
77 | $entity = new Member(); |
78 | $this->entityUtils()->createOlzEntity($entity, ['onOff' => true]); |
79 | $entity->setIdent($member_ident); |
80 | $entity->setUser($user); |
81 | $entity->setData($enc_member); |
82 | $entity->setUpdates(null); |
83 | $member_utils->update($entity, $user); |
84 | $this->entityManager()->persist($entity); |
85 | } else { |
86 | if ($entity->getData() === $enc_member && $entity->getUser() === $user) { |
87 | $member_info_by_ident[$member_ident] = [...$base_info, 'action' => 'KEEP']; |
88 | $member_utils->update($entity, $user); |
89 | } else { |
90 | $member_info_by_ident[$member_ident] = [...$base_info, 'action' => 'UPDATE']; |
91 | $this->entityUtils()->updateOlzEntity($entity, []); |
92 | $entity->setUser($user); |
93 | $entity->setData($enc_member); |
94 | $member_utils->update($entity, $user); |
95 | } |
96 | } |
97 | $new_value_by_key = json_decode($entity->getUpdates() ?? '[]', true) ?: []; |
98 | $updates = []; |
99 | foreach ($new_value_by_key as $key => $new_value) { |
100 | $updates[$key] = ['old' => $member[$key] ?? '', 'new' => $new_value]; |
101 | } |
102 | $member_info_by_ident[$member_ident]['updates'] = $updates; |
103 | } |
104 | foreach ($existing_member_is_deleted as $int_ident => $is_deleted) { |
105 | $member_ident = "{$int_ident}"; |
106 | if ($is_deleted) { |
107 | $member = $member_repo->findOneBy(['ident' => $member_ident]); |
108 | $member_info_by_ident[$member_ident] = [ |
109 | 'action' => 'DELETE', |
110 | 'username' => null, |
111 | 'matchingUsername' => null, |
112 | 'user' => $this->getUserData($member?->getUser()), |
113 | 'updates' => [], |
114 | ]; |
115 | if ($member) { |
116 | $this->entityManager()->remove($member); |
117 | } else { |
118 | $this->log()->warning("Cannot delete inexistent member: {$member_ident}"); |
119 | } |
120 | } |
121 | } |
122 | $this->entityManager()->flush(); |
123 | |
124 | $members = []; |
125 | foreach ($member_info_by_ident as $int_ident => $member) { |
126 | $member_ident = "{$int_ident}"; |
127 | $this->generalUtils()->checkNotEmpty($member_ident, 'Member ident must not be empty'); |
128 | $this->generalUtils()->checkNotEmpty($member['username'], 'Member username must not be empty'); |
129 | $this->generalUtils()->checkNotEmpty($member['matchingUsername'], 'Member matchingUsername must not be empty'); |
130 | $members[] = [ |
131 | 'ident' => "{$member_ident}", |
132 | 'action' => $member['action'], |
133 | 'username' => $member['username'] ?? null, |
134 | 'matchingUsername' => $member['matchingUsername'] ?? null, |
135 | 'user' => $member['user'] ?? null, |
136 | 'updates' => $member['updates'], |
137 | ]; |
138 | } |
139 | return ['status' => 'OK', 'members' => $members]; |
140 | } |
141 | |
142 | protected function getCsvContent(string $upload_id): string { |
143 | $data_path = $this->envUtils()->getDataPath(); |
144 | $upload_path = "{$data_path}temp/{$upload_id}"; |
145 | if (!is_file($upload_path)) { |
146 | throw new HttpError(400, 'Uploaded file not found!'); |
147 | } |
148 | $csv_content = file_get_contents($upload_path); |
149 | unlink($upload_path); |
150 | $this->generalUtils()->checkNotFalse($csv_content, "Could not read uploaded Members CSV"); |
151 | return $csv_content; |
152 | } |
153 | |
154 | /** @return ?array{ |
155 | * id: int, |
156 | * firstName: non-empty-string, |
157 | * lastName: non-empty-string, |
158 | * } |
159 | */ |
160 | protected function getUserData(?User $user): ?array { |
161 | $user_id = $user?->getId(); |
162 | if (!$user_id) { |
163 | return null; |
164 | } |
165 | return [ |
166 | 'id' => $user_id, |
167 | 'firstName' => $user->getFirstName() ?: '-', |
168 | 'lastName' => $user->getLastName() ?: '-', |
169 | ]; |
170 | } |
171 | } |