Skip to content

Commit eed638d

Browse files
committed
add default sorting to task, project, member, invitation, api token endpoints
1 parent 864f41b commit eed638d

12 files changed

+181
-6
lines changed

app/Http/Controllers/Api/V1/ApiTokenController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function index(): ApiTokenCollection
3535
/** @var Builder<Client> $query */
3636
$query->whereJsonContains('grant_types', 'personal_access');
3737
})
38+
->orderBy('created_at', 'desc')
3839
->get();
3940

4041
return new ApiTokenCollection($tokens);

app/Http/Controllers/Api/V1/InvitationController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public function index(Organization $organization, InvitationIndexRequest $reques
4141
$this->checkPermission($organization, 'invitations:view');
4242

4343
$invitations = $organization->teamInvitations()
44+
->orderBy('created_at', 'desc')
4445
->paginate(config('app.pagination_per_page_default'));
4546

4647
return InvitationCollection::make($invitations);

app/Http/Controllers/Api/V1/MemberController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function index(Organization $organization, MemberIndexRequest $request):
6060
$members = Member::query()
6161
->whereBelongsTo($organization, 'organization')
6262
->with(['user'])
63+
->orderBy('created_at', 'desc')
6364
->paginate(config('app.pagination_per_page_default'));
6465

6566
return MemberCollection::make($members);

app/Http/Controllers/Api/V1/ProjectController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ public function index(Organization $organization, ProjectIndexRequest $request):
6060
$projectsQuery->whereNull('archived_at');
6161
}
6262

63-
$projects = $projectsQuery->paginate(config('app.pagination_per_page_default'));
63+
$projects = $projectsQuery
64+
->orderBy('created_at', 'desc')
65+
->paginate(config('app.pagination_per_page_default'));
6466

6567
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
6668

app/Http/Controllers/Api/V1/ProjectMemberController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public function index(Organization $organization, Project $project): ProjectMemb
4747

4848
$projectMembers = ProjectMember::query()
4949
->whereBelongsTo($project, 'project')
50+
->orderBy('created_at', 'desc')
5051
->paginate(config('app.pagination_per_page_default'));
5152

5253
return new ProjectMemberCollection($projectMembers);

app/Http/Controllers/Api/V1/TaskController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ public function index(Organization $organization, TaskIndexRequest $request): Ta
8282
$query->whereNull('done_at');
8383
}
8484

85-
$tasks = $query->paginate(config('app.pagination_per_page_default'));
85+
$tasks = $query
86+
->orderBy('created_at', 'desc')
87+
->paginate(config('app.pagination_per_page_default'));
8688

8789
return new TaskCollection($tasks);
8890
}

tests/Unit/Endpoint/Api/V1/ApiTokenEndpointTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,31 @@ public function test_index_endpoint_returns_list_api_tokens(): void
4646
]);
4747
}
4848

49+
public function test_index_endpoint_returns_api_tokens_ordered_by_created_at_descending(): void
50+
{
51+
// Arrange
52+
$data = $this->createUserWithPermission([]);
53+
$personalAccessClient = $this->createPersonalAccessClient();
54+
$tokenOldest = Token::factory()->forUser($data->user)->forClient($personalAccessClient)->create([
55+
'created_at' => now()->subDays(3),
56+
]);
57+
$tokenNewest = Token::factory()->forUser($data->user)->forClient($personalAccessClient)->create([
58+
'created_at' => now()->subDay(),
59+
]);
60+
$tokenMiddle = Token::factory()->forUser($data->user)->forClient($personalAccessClient)->create([
61+
'created_at' => now()->subDays(2),
62+
]);
63+
Passport::actingAs($data->user);
64+
65+
// Act
66+
$response = $this->getJson(route('api.v1.api-tokens.index'));
67+
68+
// Assert
69+
$this->assertResponseCode($response, 200);
70+
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
71+
$this->assertSame([$tokenNewest->id, $tokenMiddle->id, $tokenOldest->id], $ids);
72+
}
73+
4974
public function test_store_endpoint_creates_new_api_token(): void
5075
{
5176
// Arrange

tests/Unit/Endpoint/Api/V1/InvitationEndpointTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,32 @@ public function test_index_returns_invitations_of_organization(): void
5555
]);
5656
}
5757

58+
public function test_index_returns_invitations_ordered_by_created_at_descending(): void
59+
{
60+
// Arrange
61+
$data = $this->createUserWithPermission([
62+
'invitations:view',
63+
]);
64+
$invitationOldest = OrganizationInvitation::factory()->forOrganization($data->organization)->create([
65+
'created_at' => now()->subDays(3),
66+
]);
67+
$invitationNewest = OrganizationInvitation::factory()->forOrganization($data->organization)->create([
68+
'created_at' => now()->subDay(),
69+
]);
70+
$invitationMiddle = OrganizationInvitation::factory()->forOrganization($data->organization)->create([
71+
'created_at' => now()->subDays(2),
72+
]);
73+
Passport::actingAs($data->user);
74+
75+
// Act
76+
$response = $this->getJson(route('api.v1.invitations.index', $data->organization->getKey()));
77+
78+
// Assert
79+
$response->assertStatus(200);
80+
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
81+
$this->assertSame([$invitationNewest->getKey(), $invitationMiddle->getKey(), $invitationOldest->getKey()], $ids);
82+
}
83+
5884
public function test_store_fails_if_user_has_no_permission_to_create_invitations(): void
5985
{
6086
// Arrange

tests/Unit/Endpoint/Api/V1/MemberEndpointTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ public function test_index_returns_members_of_organization(): void
5252
$response->assertStatus(200);
5353
}
5454

55+
public function test_index_returns_members_ordered_by_created_at_descending(): void
56+
{
57+
// Arrange
58+
$data = $this->createUserWithPermission([
59+
'members:view',
60+
]);
61+
$memberOldest = Member::factory()->forOrganization($data->organization)->create([
62+
'created_at' => now()->subDays(3),
63+
]);
64+
$memberNewest = Member::factory()->forOrganization($data->organization)->create([
65+
'created_at' => now()->subDay(),
66+
]);
67+
$memberMiddle = Member::factory()->forOrganization($data->organization)->create([
68+
'created_at' => now()->subDays(2),
69+
]);
70+
Passport::actingAs($data->user);
71+
72+
// Act
73+
$response = $this->getJson(route('api.v1.members.index', $data->organization->getKey()));
74+
75+
// Assert
76+
$response->assertStatus(200);
77+
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
78+
// Verify that the three explicitly created members appear in newest-first order
79+
$createdMemberIds = array_values(array_filter($ids, fn ($id) => in_array($id, [
80+
$memberOldest->getKey(),
81+
$memberNewest->getKey(),
82+
$memberMiddle->getKey(),
83+
], true)));
84+
$this->assertSame([$memberNewest->getKey(), $memberMiddle->getKey(), $memberOldest->getKey()], $createdMemberIds);
85+
}
86+
5587
public function test_update_member_fails_if_user_has_no_permission_to_update_members(): void
5688
{
5789
// Arrange

tests/Unit/Endpoint/Api/V1/ProjectEndpointTest.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ public function test_index_endpoint_returns_list_of_all_projects_of_organization
5454
$response->assertJsonCount(4, 'data');
5555
}
5656

57+
public function test_index_endpoint_returns_projects_ordered_by_created_at_descending(): void
58+
{
59+
// Arrange
60+
$data = $this->createUserWithPermission([
61+
'projects:view',
62+
'projects:view:all',
63+
]);
64+
$projectOldest = Project::factory()->forOrganization($data->organization)->create([
65+
'created_at' => now()->subDays(3),
66+
]);
67+
$projectNewest = Project::factory()->forOrganization($data->organization)->create([
68+
'created_at' => now()->subDay(),
69+
]);
70+
$projectMiddle = Project::factory()->forOrganization($data->organization)->create([
71+
'created_at' => now()->subDays(2),
72+
]);
73+
Passport::actingAs($data->user);
74+
75+
// Act
76+
$response = $this->getJson(route('api.v1.projects.index', [$data->organization->getKey()]));
77+
78+
// Assert
79+
$response->assertStatus(200);
80+
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
81+
$this->assertSame([$projectNewest->getKey(), $projectMiddle->getKey(), $projectOldest->getKey()], $ids);
82+
}
83+
5784
public function test_index_endpoint_without_filter_archived_returns_only_non_archived_projects(): void
5885
{
5986
// Arrange
@@ -211,10 +238,10 @@ public function test_index_endpoint_does_not_set_billable_rate_to_null_if_member
211238
->has('data')
212239
->has('links')
213240
->has('meta')
214-
->where('data.0.billable_rate', 112)
215-
->where('data.1.billable_rate', 112)
216-
->where('data.2.billable_rate', 113)
217-
->where('data.3.billable_rate', 113)
241+
->where('data.0.billable_rate', 113)
242+
->where('data.1.billable_rate', 113)
243+
->where('data.2.billable_rate', 112)
244+
->where('data.3.billable_rate', 112)
218245
);
219246
}
220247

0 commit comments

Comments
 (0)