Page MenuHomePhorge

D4710.diff
No OneTemporary

D4710.diff

diff --git a/src/app/Http/Controllers/API/V4/SearchController.php b/src/app/Http/Controllers/API/V4/SearchController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/SearchController.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace App\Http\Controllers\API\V4;
+
+use App\Http\Controllers\Controller;
+use App\User;
+use App\UserSetting;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+/*
+ * Note: We use a separate controller for search, as this will
+ * be different that just a user search/listing functionality,
+ * it includes aliases (and contacts), how we do the search is different too.
+ */
+
+class SearchController extends Controller
+{
+ /**
+ * Search request for user's email addresses
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function searchSelf(Request $request)
+ {
+ $user = $this->guard()->user();
+ $search = trim(request()->input('search'));
+ $with_aliases = !empty(request()->input('alias'));
+ $limit = intval(request()->input('limit'));
+
+ if ($limit <= 0) {
+ $limit = 15;
+ } elseif ($limit > 100) {
+ $limit = 100;
+ }
+
+ // Prepare the query
+ $query = User::select('email', 'id')->where('id', $user->id);
+ $aliases = DB::table('user_aliases')->select(DB::raw('alias as email, user_id as id'))
+ ->where('user_id', $user->id);
+
+ if (strlen($search)) {
+ $aliases->whereLike('alias', $search);
+ $query->whereLike('email', $search);
+ }
+
+ if ($with_aliases) {
+ $query->union($aliases);
+ }
+
+ // Execute the query
+ $result = $query->orderBy('email')->limit($limit)->get();
+
+ $result = $this->resultFormat($result);
+
+ return response()->json([
+ 'list' => $result,
+ 'count' => count($result),
+ ]);
+ }
+
+ /**
+ * Search request for addresses of all users (in an account)
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function searchUser(Request $request)
+ {
+ $user = $this->guard()->user();
+ $search = trim(request()->input('search'));
+ $with_aliases = !empty(request()->input('alias'));
+ $limit = intval(request()->input('limit'));
+
+ if ($limit <= 0) {
+ $limit = 15;
+ } elseif ($limit > 100) {
+ $limit = 100;
+ }
+
+ $wallet = $user->wallet();
+
+ // Limit users to the user's account
+ $allUsers = $wallet->entitlements()->where('entitleable_type', User::class)->select('entitleable_id')->distinct();
+
+ // Sub-query for user IDs who's names match the search criteria
+ $foundUserIds = UserSetting::select('user_id')
+ ->whereIn('key', ['first_name', 'last_name'])
+ ->whereLike('value', $search)
+ ->whereIn('user_id', $allUsers);
+
+ // Prepare the query
+ $query = User::select('email', 'id')->whereIn('id', $allUsers);
+ $aliases = DB::table('user_aliases')->select(DB::raw('alias as email, user_id as id'))
+ ->whereIn('user_id', $allUsers);
+
+ if (strlen($search)) {
+ $query->where(function ($query) use ($foundUserIds, $search) {
+ $query->whereLike('email', $search)
+ ->orWhereIn('id', $foundUserIds);
+ });
+
+ $aliases->where(function ($query) use ($foundUserIds, $search) {
+ $query->whereLike('alias', $search)
+ ->orWhereIn('user_id', $foundUserIds);
+ });
+ }
+
+ if ($with_aliases) {
+ $query->union($aliases);
+ }
+
+ // Execute the query
+ $result = $query->orderBy('email')->limit($limit)->get();
+
+ $result = $this->resultFormat($result);
+
+ return response()->json([
+ 'list' => $result,
+ 'count' => count($result),
+ ]);
+ }
+
+ /**
+ * Format the search result, inject user names
+ */
+ protected function resultFormat($result)
+ {
+ if ($result->count()) {
+ // Get user names
+ $settings = UserSetting::whereIn('key', ['first_name', 'last_name'])
+ ->whereIn('user_id', $result->pluck('id'))
+ ->get()
+ ->mapWithKeys(function ($item) {
+ return [($item->user_id . ':' . $item->key) => $item->value];
+ })
+ ->all();
+
+ // "Format" the result, include user names
+ $result = $result->map(function ($record) use ($settings) {
+ return [
+ 'email' => $record->email,
+ 'name' => trim(
+ ($settings["{$record->id}:first_name"] ?? '')
+ . ' '
+ . ($settings["{$record->id}:last_name"] ?? '')
+ ),
+ ];
+ })
+ ->sortBy(['name', 'email'])
+ ->values();
+ }
+
+ return $result;
+ }
+}
diff --git a/src/config/app.php b/src/config/app.php
--- a/src/config/app.php
+++ b/src/config/app.php
@@ -257,6 +257,7 @@
'with_resources' => (bool) env('APP_WITH_RESOURCES', true),
'with_meet' => (bool) env('APP_WITH_MEET', true),
'with_companion_app' => (bool) env('APP_WITH_COMPANION_APP', true),
+ 'with_user_search' => (bool) env('APP_WITH_USER_SEARCH', false),
'signup' => [
'email_limit' => (int) env('SIGNUP_LIMIT_EMAIL', 0),
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -183,6 +183,11 @@
Route::get('payments/has-pending', [API\V4\PaymentsController::class, 'hasPayments']);
Route::get('payments/status', [API\V4\PaymentsController::class, 'paymentStatus']);
+ Route::get('search/self', [API\V4\SearchController::class, 'searchSelf']);
+ if (\config('app.with_user_search')) {
+ Route::get('search/user', [API\V4\SearchController::class, 'searchUser']);
+ }
+
Route::post('support/request', [API\V4\SupportController::class, 'request'])
->withoutMiddleware(['auth:api', 'scope:api'])
->middleware(['api']);
diff --git a/src/tests/Feature/Controller/SearchTest.php b/src/tests/Feature/Controller/SearchTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/SearchTest.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Tests\Feature\Controller;
+
+use Tests\TestCase;
+
+class SearchTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('jane@kolabnow.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestUser('jane@kolabnow.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test searching
+ */
+ public function testSearchSelf(): void
+ {
+ // Unauth access not allowed
+ $response = $this->get("api/v4/search/self");
+ $response->assertStatus(401);
+
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+
+ // w/o aliases, w/o search
+ $response = $this->actingAs($john)->get("api/v4/search/self");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame(['email' => 'john@kolab.org', 'name' => 'John Doe'], $json['list'][0]);
+
+ // with aliases, w/o search
+ $response = $this->actingAs($john)->get("api/v4/search/self?alias=1");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(2, $json['count']);
+ $this->assertCount(2, $json['list']);
+ $this->assertSame(['email' => 'john.doe@kolab.org', 'name' => 'John Doe'], $json['list'][0]);
+ $this->assertSame(['email' => 'john@kolab.org', 'name' => 'John Doe'], $json['list'][1]);
+
+ // with aliases and search
+ $response = $this->actingAs($john)->get("api/v4/search/self?alias=1&search=doe@");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(1, $json['count']);
+ $this->assertCount(1, $json['list']);
+ $this->assertSame(['email' => 'john.doe@kolab.org', 'name' => 'John Doe'], $json['list'][0]);
+
+ // User no account owner - with aliases, w/o search
+ $response = $this->actingAs($jack)->get("api/v4/search/self?alias=1");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(2, $json['count']);
+ $this->assertCount(2, $json['list']);
+ $this->assertSame(['email' => 'jack.daniels@kolab.org', 'name' => 'Jack Daniels'], $json['list'][0]);
+ $this->assertSame(['email' => 'jack@kolab.org', 'name' => 'Jack Daniels'], $json['list'][1]);
+ }
+
+ /**
+ * Test searching
+ */
+ public function testSearchUser(): void
+ {
+ \putenv('APP_WITH_USER_SEARCH=false'); // can't be done using \config()
+ $this->refreshApplication(); // reload routes
+
+ // User search route disabled
+ $response = $this->get("api/v4/search/user");
+ $response->assertStatus(404);
+
+ \putenv('APP_WITH_USER_SEARCH=true'); // can't be done using \config()
+ $this->refreshApplication(); // reload routes
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/search/user");
+ $response->assertStatus(401);
+
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+ $jane = $this->getTestUser('jane@kolabnow.com');
+
+ // Account owner - without aliases, w/o search
+ $response = $this->actingAs($john)->get("api/v4/search/user");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $users = [
+ [
+ 'email' => 'joe@kolab.org',
+ 'name' => '',
+ ],
+ [
+ 'email' => 'ned@kolab.org',
+ 'name' => 'Edward Flanders',
+ ],
+ [
+ 'email' => 'jack@kolab.org',
+ 'name' => 'Jack Daniels',
+ ],
+ [
+ 'email' => 'john@kolab.org',
+ 'name' => 'John Doe',
+ ],
+ ];
+
+
+ $this->assertSame(count($users), $json['count']);
+ $this->assertSame($users, $json['list']);
+
+ // User no account owner, without aliases w/o search
+ $response = $this->actingAs($jack)->get("api/v4/search/user");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(count($users), $json['count']);
+ $this->assertSame($users, $json['list']);
+
+ // with aliases, w/o search
+ $response = $this->actingAs($john)->get("api/v4/search/user?alias=1");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $expected = [
+ [
+ 'email' => 'joe.monster@kolab.org',
+ 'name' => '',
+ ],
+ $users[0],
+ $users[1],
+ [
+ 'email' => 'jack.daniels@kolab.org',
+ 'name' => 'Jack Daniels',
+ ],
+ $users[2],
+ [
+ 'email' => 'john.doe@kolab.org',
+ 'name' => 'John Doe',
+ ],
+ $users[3],
+ ];
+
+ $this->assertSame(count($expected), $json['count']);
+ $this->assertSame($expected, $json['list']);
+
+ // with aliases and search
+ $response = $this->actingAs($john)->get("api/v4/search/user?alias=1&search=john");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(2, $json['count']);
+ $this->assertCount(2, $json['list']);
+ $this->assertSame(['email' => 'john.doe@kolab.org', 'name' => 'John Doe'], $json['list'][0]);
+ $this->assertSame(['email' => 'john@kolab.org', 'name' => 'John Doe'], $json['list'][1]);
+
+ // Make sure we can't find users from outside of an account
+ $response = $this->actingAs($john)->get("api/v4/search/user?alias=1&search=jane");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame(0, $json['count']);
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Oct 28, 2:23 PM (21 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10049268
Default Alt Text
D4710.diff (12 KB)

Event Timeline