2020use OCP \AppFramework \Http \DataResponse ;
2121use OCP \AppFramework \OCSController ;
2222use OCP \IRequest ;
23+ use OCP \IUser ;
24+ use OCP \IUserManager ;
2325
2426/**
2527 * @psalm-import-type ProfileFieldsValuePayload from \OCA\ProfileFields\ResponseDefinitions
28+ * @psalm-import-type ProfileFieldsLookupField from \OCA\ProfileFields\ResponseDefinitions
29+ * @psalm-import-type ProfileFieldsSearchResult from \OCA\ProfileFields\ResponseDefinitions
2630 * @psalm-import-type ProfileFieldsValueRecord from \OCA\ProfileFields\ResponseDefinitions
2731 */
2832class FieldValueAdminApiController extends OCSController {
2933 public function __construct (
3034 IRequest $ request ,
3135 private FieldDefinitionService $ fieldDefinitionService ,
3236 private FieldValueService $ fieldValueService ,
37+ private IUserManager $ userManager ,
3338 private ?string $ userId ,
3439 ) {
3540 parent ::__construct (Application::APP_ID , $ request );
@@ -41,7 +46,7 @@ public function __construct(
4146 * Return all persisted profile field values for a specific user.
4247 *
4348 * @param string $userUid User identifier whose profile field values should be listed
44- * @return DataResponse<Http::STATUS_OK, list<ProfileFieldsValueRecord>, array{}>
49+ * @return DataResponse<\OCP\AppFramework\ Http::STATUS_OK, list<ProfileFieldsValueRecord>, array{}>
4550 *
4651 * 200: User field values listed successfully
4752 */
@@ -62,7 +67,7 @@ public function index(string $userUid): DataResponse {
6267 * @param int $fieldDefinitionId Identifier of the field definition
6368 * @param array{value?: string|int|float|bool|null}|string|int|float|bool|null $value Value payload to persist
6469 * @param string|null $currentVisibility Visibility to apply to the stored value
65- * @return DataResponse<Http::STATUS_OK, ProfileFieldsValueRecord, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
70+ * @return DataResponse<\OCP\AppFramework\ Http::STATUS_OK, ProfileFieldsValueRecord, array{}>|DataResponse<\OCP\AppFramework\ Http::STATUS_BAD_REQUEST|\OCP\AppFramework\ Http::STATUS_NOT_FOUND|\OCP\AppFramework\ Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
6671 *
6772 * 200: User field value stored successfully
6873 * 400: Invalid field value payload
@@ -103,7 +108,7 @@ public function upsert(
103108 *
104109 * @param string $fieldKey Immutable key of the lookup field, such as cpf
105110 * @param array{value?: string|int|float|bool|null}|string|int|float|bool|null $fieldValue Value payload to match exactly
106- * @return DataResponse<Http::STATUS_OK, array{user_uid: string, lookup_field_key: string, fields: array<string, array{definition: array<string, mixed>, value: ProfileFieldsValueRecord}>}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_CONFLICT|Http::STATUS_NOT_FOUND|Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
111+ * @return DataResponse<\OCP\AppFramework\ Http::STATUS_OK, array{user_uid: string, lookup_field_key: string, fields: array<string, array{definition: array<string, mixed>, value: ProfileFieldsValueRecord}>}, array{}>|DataResponse<\OCP\AppFramework\ Http::STATUS_BAD_REQUEST|\OCP\AppFramework\ Http::STATUS_CONFLICT|\OCP\AppFramework\ Http::STATUS_NOT_FOUND|\OCP\AppFramework\ Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
107112 *
108113 * 200: User lookup completed successfully
109114 * 400: Invalid lookup payload
@@ -142,6 +147,62 @@ public function lookup(
142147 return new DataResponse ($ this ->serializeLookupResult ($ definition , $ matches [0 ]), Http::STATUS_OK );
143148 }
144149
150+ /**
151+ * Search users by one profile field filter
152+ *
153+ * Return a paginated list of users that match one explicit profile field filter. The response
154+ * includes only the field/value pair that produced the match, not the full profile.
155+ *
156+ * @param string $fieldKey Immutable key of the field to filter by
157+ * @param string $operator Explicit search operator, currently `eq` or `contains`
158+ * @param string|null $value Value payload to compare against the stored field value
159+ * @param int $limit Maximum number of users to return in the current page
160+ * @param int $offset Zero-based offset into the matched result set
161+ * @return DataResponse<\OCP\AppFramework\Http::STATUS_OK, ProfileFieldsSearchResult, array{}>|DataResponse<\OCP\AppFramework\Http::STATUS_BAD_REQUEST|\OCP\AppFramework\Http::STATUS_NOT_FOUND|\OCP\AppFramework\Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
162+ *
163+ * 200: User search completed successfully
164+ * 400: Invalid search filter or pagination values
165+ * 401: Authenticated admin user is required
166+ * 404: Search field definition not found
167+ */
168+ #[ApiRoute(verb: 'GET ' , url: '/api/v1/users/search ' )]
169+ public function search (
170+ string $ fieldKey ,
171+ string $ operator = 'eq ' ,
172+ ?string $ value = null ,
173+ int $ limit = 50 ,
174+ int $ offset = 0 ,
175+ ): DataResponse {
176+ if ($ this ->userId === null ) {
177+ return new DataResponse (['message ' => 'Authenticated admin user is required ' ], Http::STATUS_UNAUTHORIZED );
178+ }
179+
180+ $ definition = $ this ->fieldDefinitionService ->findByFieldKey ($ fieldKey );
181+ if ($ definition === null || !$ definition ->getActive ()) {
182+ return new DataResponse (['message ' => 'Search field definition not found ' ], Http::STATUS_NOT_FOUND );
183+ }
184+
185+ try {
186+ $ search = $ this ->fieldValueService ->searchByDefinition ($ definition , $ operator , $ value , $ limit , $ offset );
187+ } catch (InvalidArgumentException $ exception ) {
188+ return new DataResponse (['message ' => $ exception ->getMessage ()], Http::STATUS_BAD_REQUEST );
189+ }
190+
191+ $ items = array_map (
192+ fn (FieldValue $ matchedValue ): array => $ this ->serializeSearchItem ($ definition , $ matchedValue ),
193+ $ search ['matches ' ],
194+ );
195+
196+ return new DataResponse ([
197+ 'items ' => $ items ,
198+ 'pagination ' => [
199+ 'limit ' => $ limit ,
200+ 'offset ' => $ offset ,
201+ 'total ' => $ search ['total ' ],
202+ ],
203+ ], Http::STATUS_OK );
204+ }
205+
145206 /**
146207 * @return array{user_uid: string, lookup_field_key: string, fields: array<string, array{definition: array<string, mixed>, value: ProfileFieldsValueRecord}>}
147208 */
@@ -174,4 +235,31 @@ private function serializeLookupResult(FieldDefinition $lookupDefinition, FieldV
174235 'fields ' => $ fields ,
175236 ];
176237 }
238+
239+ /**
240+ * @return array{user_uid: string, display_name: string, fields: array<string, ProfileFieldsLookupField>}
241+ */
242+ private function serializeSearchItem (FieldDefinition $ definition , FieldValue $ matchedValue ): array {
243+ $ user = $ this ->userManager ->get ($ matchedValue ->getUserUid ());
244+
245+ return [
246+ 'user_uid ' => $ matchedValue ->getUserUid (),
247+ 'display_name ' => $ this ->resolveDisplayName ($ user , $ matchedValue ->getUserUid ()),
248+ 'fields ' => [
249+ $ definition ->getFieldKey () => [
250+ 'definition ' => $ definition ->jsonSerialize (),
251+ 'value ' => $ this ->fieldValueService ->serializeForResponse ($ matchedValue ),
252+ ],
253+ ],
254+ ];
255+ }
256+
257+ private function resolveDisplayName (?IUser $ user , string $ fallbackUserUid ): string {
258+ if ($ user === null ) {
259+ return $ fallbackUserUid ;
260+ }
261+
262+ $ displayName = $ user ->getDisplayName ();
263+ return $ displayName !== '' ? $ displayName : $ fallbackUserUid ;
264+ }
177265}
0 commit comments