Skip to content

Commit

Permalink
add groups feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Thiritin committed Aug 14, 2024
1 parent a7dc029 commit caa11f5
Show file tree
Hide file tree
Showing 20 changed files with 484 additions and 98 deletions.
1 change: 1 addition & 0 deletions app/Enums/GroupTypeEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum GroupTypeEnum: string
{
case Default = 'none';
case Department = 'department';
case Team = 'team';
case Automated = 'automated';

}
30 changes: 24 additions & 6 deletions app/Http/Controllers/Staff/GroupMemberController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,31 @@ class GroupMemberController extends Controller
public function index(Group $group, Request $request)
{
return Inertia::render('Staff/Groups/Tabs/MemberTab', [
'group' => $group->loadCount('users')->only(['hashid', 'name', 'users_count']),
'group' => $group->loadCount('users')->only(['hashid', 'name', 'users_count','parent_id']),
'parent' => $group->parent?->only(['hashid', 'name']),
// Sort Owner, Admin, Moderator, Member and then by name
'users' => $group->users()->withPivot('level')->get(['id', 'name', 'profile_photo_path'])->map(fn($user
'users' => $group->users()
->with([
'groups' => fn($q) => $q->where('type', 'team')->where('parent_id', $group->id)->get(['id', 'name'])
])
->withPivot('level')->get(['id', 'name', 'profile_photo_path'])->map(fn($user
) => [
'id' => $user->hashid,
'name' => $user->name,
'profile_photo_path' => (is_null($user->profile_photo_path)) ? null : Storage::drive('s3-avatars')->url($user->profile_photo_path),
'level' => $user->pivot->level,
'teams' => $user->groups->map(fn($group) => [
'id' => $group->hashid,
'name' => $group->name,
]),
'title' => $user->pivot->title,
])->sortBy(fn($user) => [
$user['level'] === 'owner' ? 0 : 1,
$user['level'] === 'admin' ? 1 : 2,
$user['level'] === 'moderator' ? 2 : 3,
$user['name']
]),
'canEdit' => $group->isAdmin($request->user())
'canEdit' => $request->user()->can('update', $group),
]);
}

Expand Down Expand Up @@ -61,10 +70,14 @@ public function store(Group $group, Request $request)
}
// If user is already in the department throw validationexception
if ($group->users->contains($user)) {
throw ValidationException::withMessages([$field => 'User is already in the department']);
throw ValidationException::withMessages([$field => 'User is already in the group']);
}
// Attach user to department
$group->users()->attach($user, ['level' => GroupUserLevel::Member]);
// If group has a parent, add them to the parent group
if ($group->parent && !$group->parent->users->contains($user)) {
$group->parent->users()->syncWithoutDetaching([$user->id => ['level' => GroupUserLevel::Member]]);
}
return redirect()->route('staff.groups.members.index', $group);
}

Expand All @@ -83,7 +96,8 @@ public function edit(Group $group, User $member)
*/
public function update(Group $group, User $member, Request $request)
{
if ($member->id == $request->user()->id) {
$isAdminOfParentGroup = $group->parent?->isAdmin($request->user());
if ($member->id == $request->user()->id && !$isAdminOfParentGroup) {
throw ValidationException::withMessages(["You cannot update your own level."]);
}

Expand All @@ -106,12 +120,16 @@ public function update(Group $group, User $member, Request $request)
*/
public function destroy(Group $group, User $member, Request $request)
{
if ($member->id === $request->user()->id) {
if ($member->id === $request->user()->id && !$group->parent?->isAdmin($request->user())) {
throw ValidationException::withMessages(["You cannot remove yourself."]);
}

$requestMember = $group->users()->find($member)->pivot;
$this->authorize('delete', $requestMember);
$group->users()->detach($member);
// If group has children, remove them from the children
if ($group->children()->exists()) {
$group->children->each(fn($child) => $child->users()->detach($member));
}
}
}
44 changes: 44 additions & 0 deletions app/Http/Controllers/Staff/GroupTeamController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace App\Http\Controllers\Staff;

use App\Enums\GroupTypeEnum;
use App\Http\Controllers\Controller;
use App\Models\Group;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Inertia\Inertia;

class GroupTeamController extends Controller
{
public function index(Group $group, Request $request)
{
Gate::authorize('view', $group);
$myDepartments = $request->user()->groups()
->where('type', GroupTypeEnum::Department)->select('id', 'level')->get()
->mapWithKeys(fn($role) => [$role->id => ucwords($role->level)]);
return Inertia::render('Staff/Groups/Tabs/TeamTab', [
'group' => $group->loadCount('users')->only(['hashid', 'name', 'users_count','parent_id']),
'parent' => $group->parent?->only(['hashid', 'name']),
'teams' => $group->children()
->where('type', GroupTypeEnum::Team)
->withCount('users')
->get(['hashid', 'name', 'users_count']),
'myGroups' => $myDepartments->values(),
'canEdit' => $group->isAdmin($request->user())
]);
}

public function store(Group $group, Request $request)
{
Gate::authorize('update', $group);
$validated = $request->validate([
'name' => 'required|string|max:255',
]);
$group->children()->create([
'name' => $validated['name'],
'type' => GroupTypeEnum::Team,
]);
return redirect()->back();
}
}
18 changes: 15 additions & 3 deletions app/Http/Controllers/Staff/GroupsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function index(Request $request)
]);

return Inertia::render('Staff/Groups/GroupsIndex', [
'groups' => $departmentsSortedByMembershipAndUserCount,
'groups' => $departmentsSortedByMembershipAndUserCount->values(),
'myGroups' => $myDepartments,
]);
}
Expand All @@ -39,9 +39,12 @@ public function show(Group $group, Request $request)
$Parsedown = new Parsedown();
$Parsedown->setSafeMode(true);
return Inertia::render('Staff/Groups/Tabs/GroupInfoTab', [
'group' => $group->loadCount('users')->only(['hashid', 'description', 'name', 'users_count']),
'group' => $group
->loadCount('users')
->only(['hashid', 'name', 'users_count', 'description','parent_id']),
'parent' => $group->parent?->only(['hashid', 'name']),
'descriptionHtml' => $Parsedown->parse($group->description),
'canEdit' => $group->isAdmin($request->user()),
'canEdit' => $request->user()->can('update', $group),
]);
}

Expand All @@ -58,4 +61,13 @@ public function update(GroupUpdateRequest $request, Group $group)
]);
return redirect()->back();
}

public function destroy(Group $group)
{
Gate::authorize('delete', $group);
$parent = $group->parent;
$group->delete();
return redirect()->route('staff.groups.show', $parent);

}
}
10 changes: 10 additions & 0 deletions app/Models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,14 @@ public function isAdmin(User $user)
}
return $member->pivot->level == GroupUserLevel::Admin || $member->pivot->level == GroupUserLevel::Owner;
}

public function children()
{
return $this->hasMany(__CLASS__, 'parent_id');
}

public function parent()
{
return $this->belongsTo(__CLASS__, 'parent_id');
}
}
19 changes: 19 additions & 0 deletions app/Observers/GroupObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Observers;

use App\Enums\GroupTypeEnum;
use App\Enums\GroupUserLevel;
use App\Models\Group;
use App\Services\NextcloudService;
Expand All @@ -11,6 +12,14 @@ class GroupObserver
{
public function created(Group $group)
{
if ($group->type === GroupTypeEnum::Team && $group->parent->nextcloud_folder_id) {
NextcloudService::createGroup($group->hashid);
// Parent->name / Group->name
NextcloudService::setDisplayName($group->hashid, $group->parent->name.' / '.$group->name);
// Add to parent group folder
NextcloudService::addGroupToFolder($group->parent->nextcloud_folder_id, $group->hashid);
return;
}
if (Auth::user()) {
$group->users()->attach(Auth::user(), [
"level" => GroupUserLevel::Owner
Expand Down Expand Up @@ -43,5 +52,15 @@ public function updated(Group $group): void
// Update the display name of the group
NextcloudService::setDisplayName($group->hashid, $group->name);
}

// Team update nextcloud dipslay name
if ($group->type === GroupTypeEnum::Team && $group->isDirty('name') && $group->parent->nextcloud_folder_id) {
NextcloudService::setDisplayName($group->hashid, $group->parent->name.' / '.$group->name);
}
}

public function deleted(Group $group)
{
NextcloudService::deleteGroup($group->hashid);
}
}
18 changes: 6 additions & 12 deletions app/Observers/GroupUserObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ public function created(GroupUser $groupUser): void
if ($groupUser->group->type === GroupTypeEnum::Department) {
CheckStaffGroupMembershipJob::dispatch($groupUser->user);
}
if ($groupUser->group->nextcloud_folder_name && !app()->runningUnitTests()) {
if (($groupUser->group->nextcloud_folder_name || $groupUser->group->parent?->nextcloud_folder_name) && !app()->runningUnitTests()) {
NextcloudService::addUserToGroup($groupUser->group, $groupUser->user);
$allowAclManagement = in_array($groupUser->level, [GroupUserLevel::Admin, GroupUserLevel::Owner]);
if ($allowAclManagement) {
if ($allowAclManagement && $groupUser->group->type !== GroupTypeEnum::Team) {
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, $allowAclManagement);
}
}
}

public function updated(GroupUser $groupUser): void
{
if ($groupUser->group->nextcloud_folder_nam && !app()->runningUnitTests()) {
if ($groupUser->group->nextcloud_folder_name && !app()->runningUnitTests()) {
if ($groupUser->isDirty('level')) {
$allowAclManagement = in_array($groupUser->level, [GroupUserLevel::Admin, GroupUserLevel::Owner]);
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, $allowAclManagement);
Expand All @@ -41,15 +41,9 @@ public function deleted(GroupUser $groupUser): void
}
if ($groupUser->group->nextcloud_folder_name && !app()->runningUnitTests()) {
NextcloudService::removeUserFromGroup($groupUser->group, $groupUser->user);
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, false);
if ($groupUser->group->type !== GroupTypeEnum::Team) {
NextcloudService::setManageAcl($groupUser->group, $groupUser->user, false);
}
}
}

public function restored(GroupUser $groupUser): void
{
}

public function forceDeleted(GroupUser $groupUser): void
{
}
}
14 changes: 11 additions & 3 deletions app/Policies/GroupPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function viewAny(User $user): bool
public function view(User $user, Group $group): Response
{
$inGroup = $user->inGroup($group->id);
$staffException = $group->type === GroupTypeEnum::Department && $user->isStaff();
$staffException = ($group->type === GroupTypeEnum::Department || $group->type === GroupTypeEnum::Team) && $user->isStaff();
$userPermission = $user->scopeCheck('groups.read');

if ($inGroup || $staffException) {
Expand All @@ -61,12 +61,20 @@ public function update(User $user, Group $group): bool
->whereGroupId($group->id)
->where(fn($q) => $q->whereLevel(GroupUserLevel::Admin)->orWhere('level', GroupUserLevel::Owner))
->exists();
return ((Auth::guard("admin")->check() && $user->can('admin.groups.update')) || ($userAdminInGroup && $user->scopeCheck('groups.update')));
$userAdminInParentGroup = GroupUser::whereUserId($user->id)
->whereGroupId($group->parent_id)
->where(fn($q) => $q->whereLevel(GroupUserLevel::Admin)->orWhere('level', GroupUserLevel::Owner))
->exists();
return ((Auth::guard("admin")->check() && $user->can('admin.groups.update')) || (($userAdminInGroup || $userAdminInParentGroup) && $user->scopeCheck('groups.update')));
}

public function delete(User $user, Group $group): bool
{
if($group->type !== GroupTypeEnum::Team) {
return false;
}
$userOwnerInGroup = GroupUser::whereUserId($user->id)->whereGroupId($group->id)->whereLevel(GroupUserLevel::Owner)->exists();
return ((Auth::guard("admin")->check() && $user->can('admin.groups.delete')) || ($userOwnerInGroup && $user->scopeCheck('groups.delete')));
$userOwnerInParentGroup = GroupUser::whereUserId($user->id)->whereGroupId($group->parent_id)->whereLevel(GroupUserLevel::Owner)->exists();
return ((Auth::guard("admin")->check() && $user->can('admin.groups.delete')) || (($userOwnerInGroup || $userOwnerInParentGroup) && $user->scopeCheck('groups.delete')));
}
}
10 changes: 8 additions & 2 deletions app/Policies/GroupUserPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ public function view(User $user, GroupUser $groupUser): bool

public function update(User $user, GroupUser $groupUserInitiator): bool
{
return $user->scopeCheck('groups.update') && $groupUserInitiator->isAdmin();
return $user->scopeCheck('groups.update') && ($groupUserInitiator->isAdmin() || $groupUserInitiator->group->parent?->isAdmin($user));
}

public function create(User $user, GroupUser $groupUserInitiator): bool
{
return $user->scopeCheck('groups.update') && $groupUserInitiator->isAdmin();
return $user->scopeCheck('groups.update') && ($groupUserInitiator->isAdmin() || $groupUserInitiator->group->parent?->isAdmin($user));
}

public function delete(User $user, GroupUser $groupUser): Response
Expand All @@ -40,6 +40,12 @@ public function delete(User $user, GroupUser $groupUser): Response
if ($groupUser->group->isAdmin($user)) {
return Response::allow();
}

// check if user is admin of parent group
if ($groupUser->group->parent && $groupUser->group->parent->isAdmin($user)) {
return Response::allow();
}

return Response::deny('Insufficient permissions, you cannot delete users.');
}
}
21 changes: 17 additions & 4 deletions app/Services/NextcloudService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public static function createGroup($groupId)
return $groupId;
}

// delete group
public static function deleteGroup($groupId)
{
Http::nextcloud()->delete("/ocs/v1.php/cloud/groups/{$groupId}")->throw();
}

public static function setDisplayName($groupId, $displayName)
{
Http::nextcloud()->put("https://cloud.eurofurence.org/ocs/v2.php/cloud/groups/{$groupId}", [
Expand All @@ -41,11 +47,10 @@ public static function addUserToGroup(Group $group, User $user)
// Check user
if (!self::checkUserExists($user->hashid)) {
self::createUser($user); // Create user also adds groups so we don't need to add them here
} else {
Http::nextcloud()->post("ocs/v2.php/cloud/users/{$user->hashid}/groups", [
"groupid" => $group->hashid,
])->throw();
}
Http::nextcloud()->post("ocs/v2.php/cloud/users/{$user->hashid}/groups", [
"groupid" => $group->hashid,
])->throw();
}

public static function removeUserFromGroup(Group $group, User $user)
Expand Down Expand Up @@ -108,6 +113,14 @@ public static function createFolder(string $folderName, string $groupId): int
return (int) $xml->data->id;
}

// add group to folder
public static function addGroupToFolder($folderId, $groupId)
{
Http::nextcloud()->post("apps/groupfolders/folders/{$folderId}/groups", [
"group" => $groupId,
])->throw();
}

public static function renameFolder(int $folderId, string $folderName): void
{
Http::nextcloud()->post("apps/groupfolders/folders/{$folderId}/mountpoint", [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

use App\Models\Group;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::table('groups', function (Blueprint $table) {
$table->foreignIdFor(Group::class, 'parent_id')->after('id')->nullable()->constrained('groups')->cascadeOnDelete();
});
}
};
Loading

0 comments on commit caa11f5

Please sign in to comment.