From 36bdb5e5addf21272c4f87e0e614868890dc51d5 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Tue, 27 Aug 2024 13:35:54 +0100 Subject: [PATCH 01/11] Add archived_at field to groups. Allow setting by Network Coordinator or Admins. Use this to filter list of groups and add a parameter to control that. --- app/Group.php | 1 + app/Http/Controllers/API/GroupController.php | 48 +++++++++-- app/Http/Resources/Group.php | 7 ++ .../2024_08_27_123452_group_archive.php | 32 ++++++++ tests/Feature/Groups/APIv2GroupTest.php | 79 +++++++++++++++++-- 5 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 database/migrations/2024_08_27_123452_group_archive.php diff --git a/app/Group.php b/app/Group.php index ce6d66834..4498211b3 100644 --- a/app/Group.php +++ b/app/Group.php @@ -47,6 +47,7 @@ class Group extends Model implements Auditable 'phone', 'network_data', 'email', + 'archived_at' ]; protected $appends = ['ShareableLink', 'auto_approve']; diff --git a/app/Http/Controllers/API/GroupController.php b/app/Http/Controllers/API/GroupController.php index 896d5ca0b..91b39d758 100644 --- a/app/Http/Controllers/API/GroupController.php +++ b/app/Http/Controllers/API/GroupController.php @@ -230,6 +230,15 @@ public static function getGroupList() * operationId="getGroupListv2", * tags={"Groups"}, * summary="Get list of group names", + * @OA\Parameter( + * name="archived", + * description="Include archived groups", + * required=false, + * in="query", + * @OA\Schema( + * type="boolean" + * ) + * ), * @OA\Response( * response=200, * description="Successful operation", @@ -251,14 +260,25 @@ public static function getGroupList() */ public static function listNamesv2(Request $request) { + $request->validate([ + 'archived' => ['string', 'in:true,false'], + ]); + // We only return the group id and name, for speed. - $groups = Group::select('idgroups', 'name')->get(); + $query = Group::select('idgroups', 'name', 'archived_at'); + + if (!$request->has('archived') || $request->get('archived') == 'false') { + $query = $query->whereNull('archived_at'); + } + + $groups = $query->get(); $ret = []; foreach ($groups as $group) { $ret[] = [ 'id' => $group->idgroups, 'name' => $group->name, + 'archived_at' => $group->archived_at ? Carbon::parse($group->archived_at)->toIso8601String() : null ]; } @@ -696,8 +716,14 @@ public function createGroupv2(Request $request) { * description="Network-defined JSON data", * property="network_data", * @OA\Schema() - * ), - * ) + * ) + * @OA\Property( + * property="archived_at", + * title="archived_at", + * description="If present, this group has been archived and is no longer active.", + * format="date-time", + * ) + * ) * ) * ), * @OA\Response( @@ -716,7 +742,9 @@ public function createGroupv2(Request $request) { public function updateGroupv2(Request $request, $idGroup) { $user = $this->getUser(); - list($name, $area, $postcode, $location, $phone, $website, $description, $timezone, $latitude, $longitude, $country, $network_data, $email) = $this->validateGroupParams( + list($name, $area, $postcode, $location, $phone, $website, $description, $timezone, + $latitude, $longitude, $country, $network_data, $email, + $archived_at) = $this->validateGroupParams( $request, false ); @@ -745,10 +773,11 @@ public function updateGroupv2(Request $request, $idGroup) { 'email' => $email, ]; - if ($user->hasRole('Administrator') || $user->hasRole('NetworkCoordinator')) { - // Not got permission to update these. - $data['area'] = $request->area; - $data['postcode'] = $request->postcode; + if ($user->hasRole('Administrator') || ($user->hasRole('NetworkCoordinator') && $isCoordinatorForGroup)) { + // Got permission to update these. + $data['area'] = $area; + $data['postcode'] = $postcode; + $data['archived_at'] = $archived_at; } if (isset($_FILES) && !empty($_FILES)) { @@ -848,6 +877,7 @@ private function validateGroupParams(Request $request, $create): array { $request->validate([ 'name' => ['max:255'], 'location' => ['max:255'], + 'archived_at' => ['date'], ]); } @@ -861,6 +891,7 @@ private function validateGroupParams(Request $request, $create): array { $timezone = $request->input('timezone'); $network_data = $request->input('network_data'); $email = $request->input('email'); + $archived_at = $request->input('archived_at'); $latitude = null; $longitude = null; @@ -901,6 +932,7 @@ private function validateGroupParams(Request $request, $create): array { $country_code, $network_data, $email, + $archived_at ); } } diff --git a/app/Http/Resources/Group.php b/app/Http/Resources/Group.php index cb847f11c..8d54b916d 100644 --- a/app/Http/Resources/Group.php +++ b/app/Http/Resources/Group.php @@ -254,6 +254,12 @@ * description="The last change to this group. This includes changes which affect the stats.", * format="date-time", * ) + * @OA\Property( + * property="archived_at", + * title="archived_at", + * description="If present, this group has been archived and is no longer active.", + * format="date-time", + * ) * ) */ class Group extends JsonResource @@ -296,6 +302,7 @@ public function toArray($request) 'network_data' => $networkData, 'full' => true, 'email' => $this->email, + 'archived_at' => $this->archived_at ? Carbon::parse($this->archived_at)->toIso8601String() : null ]; $ret['hosts'] = $this->resource->all_confirmed_hosts_count; diff --git a/database/migrations/2024_08_27_123452_group_archive.php b/database/migrations/2024_08_27_123452_group_archive.php new file mode 100644 index 000000000..573e813a9 --- /dev/null +++ b/database/migrations/2024_08_27_123452_group_archive.php @@ -0,0 +1,32 @@ +date('archived_at')->default(null)->nullable()->comment('Date the group was archived'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('groups', function (Blueprint $table) { + $table->dropColumn('archived_at'); + }); + } +}; diff --git a/tests/Feature/Groups/APIv2GroupTest.php b/tests/Feature/Groups/APIv2GroupTest.php index 41883f3c4..6947c0464 100644 --- a/tests/Feature/Groups/APIv2GroupTest.php +++ b/tests/Feature/Groups/APIv2GroupTest.php @@ -181,17 +181,23 @@ public function testCreateGroupLoggedOutWithToken() $response->assertSuccessful(); $json = json_decode($response->getContent(), true); $groups = $json['data']; + $this->assertGroupFound($groups, $idgroups, true); + } + + private function assertGroupFound($groups, $id, $shouldBeFound) { + $ix = 0; $found = false; - foreach ($groups as $g) - { - if ($group->name == $g['name']) - { + foreach ($groups as $g) { + if ($g['id'] == $id) { $found = true; + } else { + $ix++; } } - $this->assertTrue($found); + $this->assertEquals($shouldBeFound, $found); + return $ix; } public function testCreateGroupGeocodeFailure() @@ -463,4 +469,67 @@ public function testNetworkDataUpdatedAt() { $this->assertEquals($idgroups, $groups[0]['id']); $this->assertEquals((new Carbon($updated_at))->getTimestamp(), (new Carbon($groups[0]['updated_at']))->getTimestamp()); } + + public function testArchived() { + $user = User::factory()->administrator()->create([ + 'api_token' => '1234', + ]); + $this->actingAs($user); + + $this->get('/'); + $user = Auth::user(); + + $idgroups = $this->createGroup( + 'Test Group', + 'https://therestartproject.org', + 'London', + 'Some text.', + true, + true, + 'info@test.com' + ); + + // Get group - archived_at should not be set. + $response = $this->get("/api/v2/groups/$idgroups"); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $this->assertEquals($idgroups, $json['data']['id']); + $this->assertNull($json['data']['archived_at']); + + // Patch the group to set it. + $response = $this->patch('/api/v2/groups/' . $idgroups, [ + 'archived_at' => '2022-01-01', + ]); + $response->assertSuccessful(); + + // Get it back - should be set. + $response = $this->get("/api/v2/groups/$idgroups"); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $this->assertEquals($idgroups, $json['data']['id']); + $this->assertEquals('2022-01-01T00:00:00+00:00', $json['data']['archived_at']); + + // Group shouldn't appear in the list of groups by default. + $response = $this->get('/api/v2/groups/names'); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $groups = $json['data']; + + $this->assertGroupFound($groups, $idgroups, false); + + $response = $this->get('/api/v2/groups/names?archived=true'); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $groups = $json['data']; + $ix = $this->assertGroupFound($groups, $idgroups, true); + $this->assertEquals('2022-01-01T00:00:00+00:00', $groups[$ix]['archived_at']); + + $response = $this->get('/api/v2/groups/names?archived=false'); + $response->assertSuccessful(); + $json = json_decode($response->getContent(), true); + $groups = $json['data']; + $this->assertGroupFound($groups, $idgroups, false); + + $this->assertTrue(false); + } } From 010b4a2a6cc4709113109c062a12b8c6289eabfc Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 9 Sep 2024 09:39:51 +0100 Subject: [PATCH 02/11] Test fixes. --- app/Http/Controllers/API/GroupController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/API/GroupController.php b/app/Http/Controllers/API/GroupController.php index 91b39d758..70aa65379 100644 --- a/app/Http/Controllers/API/GroupController.php +++ b/app/Http/Controllers/API/GroupController.php @@ -716,14 +716,14 @@ public function createGroupv2(Request $request) { * description="Network-defined JSON data", * property="network_data", * @OA\Schema() - * ) - * @OA\Property( + * ), + * @OA\Property( * property="archived_at", * title="archived_at", * description="If present, this group has been archived and is no longer active.", * format="date-time", - * ) - * ) + * ) + * ) * ) * ), * @OA\Response( From 8004f61c392478fa67e7b4f52e407ee67c400d51 Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 9 Sep 2024 10:00:46 +0100 Subject: [PATCH 03/11] Test fixes. --- app/Http/Resources/Group.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Resources/Group.php b/app/Http/Resources/Group.php index 8d54b916d..211a9088f 100644 --- a/app/Http/Resources/Group.php +++ b/app/Http/Resources/Group.php @@ -253,7 +253,7 @@ * title="updated_at", * description="The last change to this group. This includes changes which affect the stats.", * format="date-time", - * ) + * ), * @OA\Property( * property="archived_at", * title="archived_at", From e59a2d89b2674443b8bffb9094dad5058ed82f8e Mon Sep 17 00:00:00 2001 From: Edward Hibbert Date: Mon, 9 Sep 2024 10:42:23 +0100 Subject: [PATCH 04/11] Add dropdown into group actions to archive group --- lang/en/groups.php | 2 ++ lang/fr-BE/groups.php | 2 ++ lang/fr/groups.php | 2 ++ resources/js/components/GroupActions.vue | 24 ++++++++++++++++++++++++ resources/js/components/GroupHeading.vue | 19 ++++++++++++++----- resources/js/components/GroupPage.vue | 7 ++++++- resources/views/group/view.blade.php | 2 ++ 7 files changed, 52 insertions(+), 6 deletions(-) diff --git a/lang/en/groups.php b/lang/en/groups.php index 9767dafa5..7d0a66454 100644 --- a/lang/en/groups.php +++ b/lang/en/groups.php @@ -138,7 +138,9 @@ 'edit_succeeded' => 'Group updated!', 'edit_failed' => 'Group could not be edited.', 'delete_group' => 'Delete group', + 'archive_group' => 'Archive group', 'delete_group_confirm' => 'Please confirm that you want to delete :name.', + 'archive_group_confirm' => 'Please confirm that you want to archive :name.', 'delete_succeeded' => 'Group :name has been deleted.', 'nearest_groups' => 'These are the groups that are within 50 km of :location', 'nearest_groups_change' => '(change)', diff --git a/lang/fr-BE/groups.php b/lang/fr-BE/groups.php index 7c506752e..48471801e 100644 --- a/lang/fr-BE/groups.php +++ b/lang/fr-BE/groups.php @@ -139,6 +139,8 @@ 'edit_failed' => 'Le Repair Café n\'a pas pu être édité.', 'delete_group' => 'Supprimer Repair Café', 'delete_group_confirm' => 'Veuillez confirmer que vous voulez supprimer :name', + 'archive_group' => 'Archivez Repair Café', + 'archive_group_confirm' => 'Veuillez confirmer que vous voulez archivez :name', 'delete_succeeded' => 'Repair Café :name a été supprimé.', 'duplicate' => 'Ce nom (:name) existe déjà. Si c\'est le vôtre, veuillez vous rendre dans la page Repair Cafés, utiliser le menu et l\'éditer.', 'geocode_failed' => 'Lieu non trouvé. Si vous ne parvenez pas à trouver le lieu où se trouve votre Repair Café, essayez d\'indiquer un lieu plus général (tel qu\'un village ou une ville) ou une adresse spécifique, plutôt qu\'un nom de bâtiment.', diff --git a/lang/fr/groups.php b/lang/fr/groups.php index fdcc747dd..109f91a0a 100644 --- a/lang/fr/groups.php +++ b/lang/fr/groups.php @@ -139,6 +139,8 @@ 'edit_failed' => 'Le Repair Café n\'a pas pu être édité.', 'delete_group' => 'Supprimer Repair Café', 'delete_group_confirm' => 'Veuillez confirmer que vous voulez supprimer :name', + 'archive_group' => 'Archivez Repair Café', + 'archive_group_confirm' => 'Veuillez confirmer que vous voulez archivez :name', 'delete_succeeded' => 'Repair Café :name a été supprimé.', 'duplicate' => 'Ce nom (:name) existe déjà. Si c\'est le vôtre, veuillez vous rendre dans la page Repair Cafés, utiliser le menu et l\'éditer.', 'geocode_failed' => 'Lieu non trouvé. Si vous ne parvenez pas à trouver le lieu où se trouve votre Repair Café, essayez d\'indiquer un lieu plus général (tel qu\'un village ou une ville) ou une adresse spécifique, plutôt qu\'un nom de bâtiment.', diff --git a/resources/js/components/GroupActions.vue b/resources/js/components/GroupActions.vue index 544354e38..adee88678 100644 --- a/resources/js/components/GroupActions.vue +++ b/resources/js/components/GroupActions.vue @@ -32,6 +32,9 @@ {{ __('groups.delete_group') }} + + {{ __('groups.archive_group') }} +
@@ -54,6 +57,9 @@ +
\ No newline at end of file diff --git a/resources/js/components/GroupDescription.vue b/resources/js/components/GroupDescription.vue index d1c4562f9..41e9447c7 100644 --- a/resources/js/components/GroupDescription.vue +++ b/resources/js/components/GroupDescription.vue @@ -4,6 +4,7 @@ {{ __('groups.about') }}