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/GroupTags.php b/app/GroupTags.php
index c3466a75c..4c7a91e3c 100644
--- a/app/GroupTags.php
+++ b/app/GroupTags.php
@@ -9,8 +9,6 @@ class GroupTags extends Model
{
use HasFactory;
- // This is a magic value which is set on the live system, and is used to identify inactive groups.
- // It's not ideal to have this hardcoded, but the data exists.
const INACTIVE = 10;
protected $table = 'group_tags';
diff --git a/app/Http/Controllers/API/GroupController.php b/app/Http/Controllers/API/GroupController.php
index e1188b6a9..655ef476b 100644
--- a/app/Http/Controllers/API/GroupController.php
+++ b/app/Http/Controllers/API/GroupController.php
@@ -231,6 +231,15 @@ public static function getGroupList()
* operationId="getGroupListv2",
* tags={"Groups"},
* summary="Get list of group names",
+ * @OA\Parameter(
+ * name="includeArchived",
+ * description="Include archived groups",
+ * required=false,
+ * in="query",
+ * @OA\Schema(
+ * type="boolean"
+ * )
+ * ),
* @OA\Response(
* response=200,
* description="Successful operation",
@@ -252,14 +261,25 @@ public static function getGroupList()
*/
public static function listNamesv2(Request $request) {
+ $request->validate([
+ 'includeArchived' => ['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('includeArchived') || $request->get('includeArchived') == '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
];
}
@@ -826,7 +846,13 @@ public function createGroupv2(Request $request) {
* 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(
@@ -845,7 +871,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
);
@@ -874,10 +902,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)) {
@@ -977,6 +1006,7 @@ private function validateGroupParams(Request $request, $create): array {
$request->validate([
'name' => ['max:255'],
'location' => ['max:255'],
+ 'archived_at' => ['date'],
]);
}
@@ -990,6 +1020,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;
@@ -1030,6 +1061,7 @@ private function validateGroupParams(Request $request, $create): array {
$country_code,
$network_data,
$email,
+ $archived_at
);
}
}
diff --git a/app/Http/Controllers/API/NetworkController.php b/app/Http/Controllers/API/NetworkController.php
index d07c457e6..4fc6d8cd9 100644
--- a/app/Http/Controllers/API/NetworkController.php
+++ b/app/Http/Controllers/API/NetworkController.php
@@ -131,6 +131,15 @@ public function getNetworkv2($id)
* type="boolean"
* )
* ),
+ * @OA\Parameter(
+ * name="includeArchived",
+ * description="Include archived groups",
+ * required=false,
+ * in="query",
+ * @OA\Schema(
+ * type="boolean"
+ * )
+ * ),
* @OA\Response(
* response=200,
* description="Successful operation",
@@ -171,11 +180,16 @@ public function getNetworkGroupsv2(Request $request, $id)
// We use a query rather than $network->groups so that the filtering by date is done in the database rather
// than getting all groups and filtering in PHP. This is faster.
- $groups = Group::join('group_network', 'group_network.group_id', '=', 'groups.idgroups')
+ $query = Group::join('group_network', 'group_network.group_id', '=', 'groups.idgroups')
->where('group_network.network_id', $id)
->where('groups.updated_at', '>=', $start)
- ->where('groups.updated_at', '<=', $end)->get();
+ ->where('groups.updated_at', '<=', $end);
+
+ if (!$request->has('includeArchived') || $request->get('includeArchived') == 'false') {
+ $query = $query->whereNull('archived_at');
+ }
+ $groups = $query->get();
if ($request->get('includeDetails', false)) {
return \App\Http\Resources\GroupCollection::make($groups);
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index ccbd9ae74..bf41ee9e3 100644
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -51,7 +51,7 @@ public function index()
->whereNull('users_groups.deleted_at')
->orderBy('groups.name', 'ASC')
->groupBy('groups.idgroups', 'groups.name')
- ->select(['groups.idgroups', 'groups.name', 'users_groups.role'])
+ ->select(['groups.idgroups', 'groups.name', 'users_groups.role', 'groups.archived_at'])
->take(5)
->get();
diff --git a/app/Http/Controllers/GroupController.php b/app/Http/Controllers/GroupController.php
index 50b699bdf..4e6b197e2 100644
--- a/app/Http/Controllers/GroupController.php
+++ b/app/Http/Controllers/GroupController.php
@@ -35,6 +35,7 @@
use Illuminate\Support\Facades\Log;
use Notification;
use Spatie\ValidationRules\Rules\Delimited;
+use Carbon\Carbon;
class GroupController extends Controller
{
@@ -530,6 +531,7 @@ public static function expandGroups($groups, $your_groupids, $nearby_groupids)
'group_tags' => $group->group_tags()->get()->pluck('id'),
'following' => in_array($group->idgroups, $your_groupids),
'nearby' => in_array($group->idgroups, $nearby_groupids),
+ 'archived_at' => $group->archived_at ? Carbon::parse($group->archived_at)->toIso8601String() : null
];
}
}
diff --git a/app/Http/Resources/Group.php b/app/Http/Resources/Group.php
index cb847f11c..211a9088f 100644
--- a/app/Http/Resources/Group.php
+++ b/app/Http/Resources/Group.php
@@ -253,6 +253,12 @@
* 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",
+ * description="If present, this group has been archived and is no longer active.",
+ * format="date-time",
* )
* )
*/
@@ -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/app/Http/Resources/GroupSummary.php b/app/Http/Resources/GroupSummary.php
index d37a6fae1..2d8a493d9 100644
--- a/app/Http/Resources/GroupSummary.php
+++ b/app/Http/Resources/GroupSummary.php
@@ -67,7 +67,13 @@
* description="Indicates that this is a summary result, not full group information.",
* format="boolean",
* example="true"
- * )
+ * ),
+ * @OA\Property(
+ * property="archived_at",
+ * title="archived_at",
+ * description="If present, this group has been archived and is no longer active.",
+ * format="date-time",
+ * ),
* )
*/
@@ -88,6 +94,7 @@ public function toArray($request)
'location' => new GroupLocation($this),
'networks' => new NetworkSummaryCollection($this->resource->networks),
'updated_at' => Carbon::parse($this->updated_at)->toIso8601String(),
+ 'archived_at' => $this->archived_at ? Carbon::parse($this->archived_at)->toIso8601String() : null,
'summary' => true
];
diff --git a/app/Listeners/AddUserToDiscourseGroup.php b/app/Listeners/AddUserToDiscourseGroup.php
index f37186e3f..fc70a4514 100644
--- a/app/Listeners/AddUserToDiscourseGroup.php
+++ b/app/Listeners/AddUserToDiscourseGroup.php
@@ -31,6 +31,11 @@ public function handle(UserFollowedGroup $event)
return;
}
+ if ($event->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
// add user to the network groups for the group the user followed.
$repairGroup = $event->group;
$user = $event->user;
diff --git a/app/Listeners/AddUserToDiscourseThreadForEvent.php b/app/Listeners/AddUserToDiscourseThreadForEvent.php
index b0c184d93..486876880 100644
--- a/app/Listeners/AddUserToDiscourseThreadForEvent.php
+++ b/app/Listeners/AddUserToDiscourseThreadForEvent.php
@@ -42,6 +42,11 @@ public function handle(UserConfirmedEvent $e) {
$event = Party::find($e->idevents);
$user = User::find($e->iduser);
+ if ($event->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
// Might not exist - timing windows.
if ($event && $user && $event->discourse_thread) {
// We need a host of the event to add the user to the thread.
diff --git a/app/Listeners/CreateDiscourseGroupForGroup.php b/app/Listeners/CreateDiscourseGroupForGroup.php
index dbf368e1b..f7693f366 100644
--- a/app/Listeners/CreateDiscourseGroupForGroup.php
+++ b/app/Listeners/CreateDiscourseGroupForGroup.php
@@ -34,6 +34,11 @@ public function handle(ApproveGroup $event)
return;
}
+ if ($event->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
$group = $event->group;
$group->createDiscourseGroup();
}
diff --git a/app/Listeners/CreateDiscourseThreadForEvent.php b/app/Listeners/CreateDiscourseThreadForEvent.php
index 6ca1205cc..4ca7920a9 100644
--- a/app/Listeners/CreateDiscourseThreadForEvent.php
+++ b/app/Listeners/CreateDiscourseThreadForEvent.php
@@ -43,6 +43,11 @@ public function handle(ApproveEvent $event)
return;
}
+ if ($theParty->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
// Get the user who created the event.
$users = EventsUsers::where('event', $partyId)->get();
diff --git a/app/Listeners/CreateWordpressPostForEvent.php b/app/Listeners/CreateWordpressPostForEvent.php
index ca87d38da..4f8af507f 100644
--- a/app/Listeners/CreateWordpressPostForEvent.php
+++ b/app/Listeners/CreateWordpressPostForEvent.php
@@ -43,6 +43,11 @@ public function handle(ApproveEvent $event)
return;
}
+ if ($theParty->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
$theParty->approved = true;
$theParty->save();
diff --git a/app/Listeners/CreateWordpressPostForGroup.php b/app/Listeners/CreateWordpressPostForGroup.php
index f488bfccc..61ffed8bc 100644
--- a/app/Listeners/CreateWordpressPostForGroup.php
+++ b/app/Listeners/CreateWordpressPostForGroup.php
@@ -41,6 +41,11 @@ public function handle(ApproveGroup $event)
return;
}
+ if ($group->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
$group->approved = true;
$group->save();
diff --git a/app/Listeners/DeleteEventFromWordPress.php b/app/Listeners/DeleteEventFromWordPress.php
index f4a619955..d1e452dae 100644
--- a/app/Listeners/DeleteEventFromWordPress.php
+++ b/app/Listeners/DeleteEventFromWordPress.php
@@ -35,6 +35,11 @@ public function handle(EventDeleted $event)
$repairEvent = $event->repairEvent;
+ if ($repairEvent->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
try {
if ($repairEvent->shouldPushToWordPress() && ! empty($repairEvent->wordpress_post_id)) {
$this->wpClient->deletePost($repairEvent->wordpress_post_id);
diff --git a/app/Listeners/EditWordpressPostForEvent.php b/app/Listeners/EditWordpressPostForEvent.php
index 8152b0a5c..1ffeede98 100644
--- a/app/Listeners/EditWordpressPostForEvent.php
+++ b/app/Listeners/EditWordpressPostForEvent.php
@@ -44,6 +44,11 @@ public function handle(EditEvent $event)
return;
}
+ if ($theParty->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
try {
if (is_numeric($theParty->wordpress_post_id)) {
$startTimestamp = strtotime($theParty->event_start_utc);
diff --git a/app/Listeners/EditWordpressPostForGroup.php b/app/Listeners/EditWordpressPostForGroup.php
index fa3ba5edc..e10527db1 100644
--- a/app/Listeners/EditWordpressPostForGroup.php
+++ b/app/Listeners/EditWordpressPostForGroup.php
@@ -41,6 +41,11 @@ public function handle(EditGroup $event)
return;
}
+ if ($group->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
try {
if (is_numeric($group->wordpress_post_id)) {
$custom_fields = [
diff --git a/app/Listeners/NotifyApprovedEvent.php b/app/Listeners/NotifyApprovedEvent.php
index 17c25e622..3e712d6e4 100644
--- a/app/Listeners/NotifyApprovedEvent.php
+++ b/app/Listeners/NotifyApprovedEvent.php
@@ -35,6 +35,11 @@ public function handle(ApproveEvent $event)
$group = Group::findOrFail($theParty->group);
+ if ($group->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
// Only send notifications if the event is in the future.
// We don't want to send emails to Restarters about past events being added.
if ($theParty->isUpcoming()) {
diff --git a/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php b/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php
index d19f093be..337109706 100644
--- a/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php
+++ b/app/Listeners/RemoveUserFromDiscourseThreadForEvent.php
@@ -32,6 +32,11 @@ public function handle(UserLeftEvent $e) {
$event = Party::find($e->idevents);
$user = User::find($e->iduser);
+ if ($event->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
// Might not exist - timing windows.
if ($event && $user && $event->discourse_thread) {
// We need a host of the event to add the user to the thread.
diff --git a/app/Listeners/SendAdminModerateEventPhotosNotification.php b/app/Listeners/SendAdminModerateEventPhotosNotification.php
index 4dcbe29fc..2e69775a9 100644
--- a/app/Listeners/SendAdminModerateEventPhotosNotification.php
+++ b/app/Listeners/SendAdminModerateEventPhotosNotification.php
@@ -41,6 +41,11 @@ public function handle(EventImagesUploaded $event)
$this->event = $event;
$this->party = $event->party;
+ if ($this->party->theGroup->archived_at) {
+ // Suppress notifications for archived groups.
+ return;
+ }
+
Fixometer::usersWhoHavePreference('admin-moderate-event-photos')->each(function (User $user) {
if ($this->shouldSendNotification($user)) {
$user->notify(new AdminModerationEventPhotos([
diff --git a/app/User.php b/app/User.php
index 72651e402..aa5b84d19 100644
--- a/app/User.php
+++ b/app/User.php
@@ -129,12 +129,8 @@ public function groupsNearby($numberOfGroups = 10, $createdSince = null, $nearby
$groupsNearbyQuery = Group::select(
DB::raw('*, ( 6371 * acos( cos( radians('.$this->latitude.') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('.$this->longitude.') ) + sin( radians('.$this->latitude.') ) * sin( radians( latitude ) ) ) ) AS dist')
- )->leftJoin('grouptags_groups', function ($join) {
- // Exclude any groups tagged with the special value of 10, which is 'Inactive'.
- $join->on('group', '=', 'idgroups');
- $join->on('group_tag', '=', DB::raw(GroupTags::INACTIVE));
- })->where(function ($q) {
- $q->whereNull('grouptags_groups.id');
+ )->where(function ($q) {
+ $q->whereNull('archived_at');
// Only show approved groups.
$q->where('approved', true);
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/database/migrations/2024_09_16_093650_inactive_groups.php b/database/migrations/2024_09_16_093650_inactive_groups.php
new file mode 100644
index 000000000..37db48125
--- /dev/null
+++ b/database/migrations/2024_09_16_093650_inactive_groups.php
@@ -0,0 +1,46 @@
+join('group_tags', 'grouptags_groups.group_tag', '=', 'group_tags.id')
+ ->where('group_tags.id', \App\GroupTags::INACTIVE)
+ ->get();
+
+ foreach ($groups as $group) {
+ $group->archived_at = $group->updated_at;
+
+ // Remove [INACTIVE] from the group name - this is now indicated via archived_at.
+ $group->name = str_replace('[INACTIVE] ', '', $group->name);
+ $group->name = str_replace('[INACTIVE]', '', $group->name);
+ $group->save();
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ // Add [INACTIVE] into all groups with archived_at.
+ $groups = \App\Group::whereNotNull('archived_at')->get();
+
+ foreach ($groups as $group) {
+ $group->name = '[INACTIVE] ' . $group->name;
+ $group->save();
+ }
+ }
+};
diff --git a/lang/en/groups.php b/lang/en/groups.php
index be3913bee..65fc4d793 100644
--- a/lang/en/groups.php
+++ b/lang/en/groups.php
@@ -133,7 +133,11 @@
'create_failed' => 'Group could not be created. Please look at the reported errors, correct them, and try again.',
'edit_failed' => 'Group could not be edited.',
'delete_group' => 'Delete group',
+ 'archive_group' => 'Archive group',
+ 'archived_group' => 'Archived',
+ 'archived_group_title' => 'This group was archived on :date.',
'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 88882c1a3..712a888b0 100644
--- a/lang/fr-BE/groups.php
+++ b/lang/fr-BE/groups.php
@@ -135,6 +135,10 @@
'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',
+ 'archived_group' => 'Archivé',
+ 'archived_group_title' => 'Ce Repair Café a été archivé sur :date.',
'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 b8853ac43..b336b6474 100644
--- a/lang/fr/groups.php
+++ b/lang/fr/groups.php
@@ -135,6 +135,10 @@
'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',
+ 'archived_group' => 'Archivé',
+ 'archived_group_title' => 'Ce Repair Café a été archivé sur :date.',
'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/DashboardGroup.vue b/resources/js/components/DashboardGroup.vue
index 7e3ec8ec9..2bc620c88 100644
--- a/resources/js/components/DashboardGroup.vue
+++ b/resources/js/components/DashboardGroup.vue
@@ -5,6 +5,7 @@
@@ -43,9 +44,10 @@ import group from '../mixins/group' import ExternalLink from './ExternalLink' import CollapsibleSection from './CollapsibleSection' import ReadMore from './ReadMore' +import GroupArchivedBadge from "./GroupArchivedBadge.vue"; export default { - components: {ReadMore, CollapsibleSection, ExternalLink}, + components: {GroupArchivedBadge, ReadMore, CollapsibleSection, ExternalLink}, mixins: [ map, group ], props: { idgroups: { diff --git a/resources/js/components/GroupHeading.vue b/resources/js/components/GroupHeading.vue index fbf6b3a15..ecb1401f1 100644 --- a/resources/js/components/GroupHeading.vue +++ b/resources/js/components/GroupHeading.vue @@ -2,12 +2,14 @@