mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-16 12:12:15 +01:00
add default sorting to task, project, member, invitation, api token endpoints
This commit is contained in:
@@ -35,6 +35,7 @@ class ApiTokenController extends Controller
|
|||||||
/** @var Builder<Client> $query */
|
/** @var Builder<Client> $query */
|
||||||
$query->whereJsonContains('grant_types', 'personal_access');
|
$query->whereJsonContains('grant_types', 'personal_access');
|
||||||
})
|
})
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return new ApiTokenCollection($tokens);
|
return new ApiTokenCollection($tokens);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class InvitationController extends Controller
|
|||||||
$this->checkPermission($organization, 'invitations:view');
|
$this->checkPermission($organization, 'invitations:view');
|
||||||
|
|
||||||
$invitations = $organization->teamInvitations()
|
$invitations = $organization->teamInvitations()
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
->paginate(config('app.pagination_per_page_default'));
|
->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
return InvitationCollection::make($invitations);
|
return InvitationCollection::make($invitations);
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class MemberController extends Controller
|
|||||||
$members = Member::query()
|
$members = Member::query()
|
||||||
->whereBelongsTo($organization, 'organization')
|
->whereBelongsTo($organization, 'organization')
|
||||||
->with(['user'])
|
->with(['user'])
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
->paginate(config('app.pagination_per_page_default'));
|
->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
return MemberCollection::make($members);
|
return MemberCollection::make($members);
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ class ProjectController extends Controller
|
|||||||
$projectsQuery->whereNull('archived_at');
|
$projectsQuery->whereNull('archived_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
$projects = $projectsQuery->paginate(config('app.pagination_per_page_default'));
|
$projects = $projectsQuery
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
|
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class ProjectMemberController extends Controller
|
|||||||
|
|
||||||
$projectMembers = ProjectMember::query()
|
$projectMembers = ProjectMember::query()
|
||||||
->whereBelongsTo($project, 'project')
|
->whereBelongsTo($project, 'project')
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
->paginate(config('app.pagination_per_page_default'));
|
->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
return new ProjectMemberCollection($projectMembers);
|
return new ProjectMemberCollection($projectMembers);
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ class TaskController extends Controller
|
|||||||
$query->whereNull('done_at');
|
$query->whereNull('done_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
$tasks = $query->paginate(config('app.pagination_per_page_default'));
|
$tasks = $query
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
return new TaskCollection($tasks);
|
return new TaskCollection($tasks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,31 @@ class ApiTokenEndpointTest extends ApiEndpointTestAbstract
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_endpoint_returns_api_tokens_ordered_by_created_at_descending(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([]);
|
||||||
|
$personalAccessClient = $this->createPersonalAccessClient();
|
||||||
|
$tokenOldest = Token::factory()->forUser($data->user)->forClient($personalAccessClient)->create([
|
||||||
|
'created_at' => now()->subDays(3),
|
||||||
|
]);
|
||||||
|
$tokenNewest = Token::factory()->forUser($data->user)->forClient($personalAccessClient)->create([
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
$tokenMiddle = Token::factory()->forUser($data->user)->forClient($personalAccessClient)->create([
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.api-tokens.index'));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertResponseCode($response, 200);
|
||||||
|
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
|
||||||
|
$this->assertSame([$tokenNewest->id, $tokenMiddle->id, $tokenOldest->id], $ids);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_store_endpoint_creates_new_api_token(): void
|
public function test_store_endpoint_creates_new_api_token(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@@ -55,6 +55,32 @@ class InvitationEndpointTest extends ApiEndpointTestAbstract
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_returns_invitations_ordered_by_created_at_descending(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'invitations:view',
|
||||||
|
]);
|
||||||
|
$invitationOldest = OrganizationInvitation::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(3),
|
||||||
|
]);
|
||||||
|
$invitationNewest = OrganizationInvitation::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
$invitationMiddle = OrganizationInvitation::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.invitations.index', $data->organization->getKey()));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
|
||||||
|
$this->assertSame([$invitationNewest->getKey(), $invitationMiddle->getKey(), $invitationOldest->getKey()], $ids);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_store_fails_if_user_has_no_permission_to_create_invitations(): void
|
public function test_store_fails_if_user_has_no_permission_to_create_invitations(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@@ -52,6 +52,38 @@ class MemberEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$response->assertStatus(200);
|
$response->assertStatus(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_returns_members_ordered_by_created_at_descending(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'members:view',
|
||||||
|
]);
|
||||||
|
$memberOldest = Member::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(3),
|
||||||
|
]);
|
||||||
|
$memberNewest = Member::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
$memberMiddle = Member::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.members.index', $data->organization->getKey()));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
|
||||||
|
// Verify that the three explicitly created members appear in newest-first order
|
||||||
|
$createdMemberIds = array_values(array_filter($ids, fn ($id) => in_array($id, [
|
||||||
|
$memberOldest->getKey(),
|
||||||
|
$memberNewest->getKey(),
|
||||||
|
$memberMiddle->getKey(),
|
||||||
|
], true)));
|
||||||
|
$this->assertSame([$memberNewest->getKey(), $memberMiddle->getKey(), $memberOldest->getKey()], $createdMemberIds);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_update_member_fails_if_user_has_no_permission_to_update_members(): void
|
public function test_update_member_fails_if_user_has_no_permission_to_update_members(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@@ -54,6 +54,33 @@ class ProjectEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$response->assertJsonCount(4, 'data');
|
$response->assertJsonCount(4, 'data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_endpoint_returns_projects_ordered_by_created_at_descending(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'projects:view',
|
||||||
|
'projects:view:all',
|
||||||
|
]);
|
||||||
|
$projectOldest = Project::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(3),
|
||||||
|
]);
|
||||||
|
$projectNewest = Project::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
$projectMiddle = Project::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.projects.index', [$data->organization->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
|
||||||
|
$this->assertSame([$projectNewest->getKey(), $projectMiddle->getKey(), $projectOldest->getKey()], $ids);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_index_endpoint_without_filter_archived_returns_only_non_archived_projects(): void
|
public function test_index_endpoint_without_filter_archived_returns_only_non_archived_projects(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
@@ -211,10 +238,10 @@ class ProjectEndpointTest extends ApiEndpointTestAbstract
|
|||||||
->has('data')
|
->has('data')
|
||||||
->has('links')
|
->has('links')
|
||||||
->has('meta')
|
->has('meta')
|
||||||
->where('data.0.billable_rate', 112)
|
->where('data.0.billable_rate', 113)
|
||||||
->where('data.1.billable_rate', 112)
|
->where('data.1.billable_rate', 113)
|
||||||
->where('data.2.billable_rate', 113)
|
->where('data.2.billable_rate', 112)
|
||||||
->where('data.3.billable_rate', 113)
|
->where('data.3.billable_rate', 112)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,36 @@ class ProjectMemberEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$response->assertJsonCount(4, 'data');
|
$response->assertJsonCount(4, 'data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_endpoint_returns_project_members_ordered_by_created_at_descending(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'project-members:view',
|
||||||
|
]);
|
||||||
|
$project = Project::factory()->forOrganization($data->organization)->create();
|
||||||
|
$pmOldest = ProjectMember::factory()->forProject($project)->create([
|
||||||
|
'created_at' => now()->subDays(3),
|
||||||
|
]);
|
||||||
|
$pmNewest = ProjectMember::factory()->forProject($project)->create([
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
$pmMiddle = ProjectMember::factory()->forProject($project)->create([
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.project-members.index', [
|
||||||
|
$data->organization->getKey(),
|
||||||
|
$project->getKey(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
|
||||||
|
$this->assertSame([$pmNewest->getKey(), $pmMiddle->getKey(), $pmOldest->getKey()], $ids);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_store_endpoint_fails_if_user_has_no_permission_to_add_members_to_project(): void
|
public function test_store_endpoint_fails_if_user_has_no_permission_to_add_members_to_project(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@@ -78,6 +78,33 @@ class TaskEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$response->assertJsonCount(4, 'data');
|
$response->assertJsonCount(4, 'data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_endpoint_returns_tasks_ordered_by_created_at_descending(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'tasks:view',
|
||||||
|
'tasks:view:all',
|
||||||
|
]);
|
||||||
|
$taskOldest = Task::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(3),
|
||||||
|
]);
|
||||||
|
$taskNewest = Task::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
$taskMiddle = Task::factory()->forOrganization($data->organization)->create([
|
||||||
|
'created_at' => now()->subDays(2),
|
||||||
|
]);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.tasks.index', [$data->organization->getKey(), 'done' => 'all']));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$ids = collect($response->json('data'))->pluck('id')->values()->toArray();
|
||||||
|
$this->assertSame([$taskNewest->getKey(), $taskMiddle->getKey(), $taskOldest->getKey()], $ids);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_index_endpoint_without_filter_done_returns_list_of_all_tasks_of_organization(): void
|
public function test_index_endpoint_without_filter_done_returns_list_of_all_tasks_of_organization(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
Reference in New Issue
Block a user