Skip to content

Commit 2982aa2

Browse files
committed
add backend for listing user group permissions
1 parent 3756aa0 commit 2982aa2

File tree

13 files changed

+136
-28
lines changed

13 files changed

+136
-28
lines changed

app/Actions/Sharing/Propagate.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private function applyUpdate(Album $album): void
4949
// for each descendant, create a new permission if it does not exist.
5050
// or update the existing permission.
5151
$descendants = $album->descendants()->getQuery()->select('id')->pluck('id');
52-
$permissions = $album->access_permissions()->whereNotNull('user_id')->get();
52+
$permissions = $album->access_permissions()->whereNotNull('user_id')->orWhereNotNull('user_group_id')->get();
5353

5454
// This is super inefficient.
5555
// It would be better to do it in a single query...
@@ -59,6 +59,7 @@ private function applyUpdate(Album $album): void
5959
$perm = AccessPermission::updateOrCreate([
6060
APC::BASE_ALBUM_ID => $descendant,
6161
APC::USER_ID => $permission->user_id,
62+
APC::USER_GROUP_ID => $permission->user_group_id,
6263
], [
6364
APC::GRANTS_FULL_PHOTO_ACCESS => $permission->grants_full_photo_access,
6465
APC::GRANTS_DOWNLOAD => $permission->grants_download,
@@ -122,7 +123,7 @@ private function applyOverwrite(Album $album): void
122123
->where('_rgt', '<', $album->_rgt)
123124
->pluck('id');
124125

125-
$access_permissions = $album->access_permissions()->whereNotNull('user_id')->get();
126+
$access_permissions = $album->access_permissions()->whereNotNull('user_id')->orWhereNotNull('user_group_id')->get();
126127

127128
$new_perm = $access_permissions->reduce(
128129
fn (?array $acc, AccessPermission $permission) => array_merge(
@@ -131,6 +132,7 @@ private function applyOverwrite(Album $album): void
131132
fn ($descendant_id) => [
132133
APC::BASE_ALBUM_ID => $descendant_id,
133134
APC::USER_ID => $permission->user_id,
135+
APC::USER_GROUP_ID => $permission->user_group_id,
134136
APC::GRANTS_FULL_PHOTO_ACCESS => $permission->grants_full_photo_access,
135137
APC::GRANTS_DOWNLOAD => $permission->grants_download,
136138
APC::GRANTS_UPLOAD => $permission->grants_upload,

app/Actions/Sharing/Share.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@ class Share
1717
* Create an access permission from a resource.
1818
*
1919
* @param AccessPermissionResource $access_permission_resource
20+
* @param int|null $user_id
21+
* @param int|null $user_group_id
2022
* @param string $base_album_id
2123
*
2224
* @return AccessPermission
2325
*/
24-
public function do(AccessPermissionResource $access_permission_resource, int $user_id, string $base_album_id): AccessPermission
25-
{
26+
public function do(
27+
AccessPermissionResource $access_permission_resource,
28+
string $base_album_id,
29+
?int $user_id = null,
30+
?int $user_group_id = null,
31+
): AccessPermission {
2632
$perm = new AccessPermission();
2733
$perm->user_id = $user_id;
34+
$perm->user_group_id = $user_group_id;
2835
$perm->base_album_id = $base_album_id;
2936
$perm->grants_full_photo_access = $access_permission_resource->grants_full_photo_access;
3037
$perm->grants_download = $access_permission_resource->grants_download;
@@ -33,6 +40,7 @@ public function do(AccessPermissionResource $access_permission_resource, int $us
3340
$perm->grants_delete = $access_permission_resource->grants_delete;
3441
$perm->load('user');
3542
$perm->load('album');
43+
$perm->load('user_group');
3644
$perm->save();
3745

3846
return $perm;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2025 LycheeOrg.
7+
*/
8+
9+
namespace App\Contracts\Http\Requests;
10+
11+
interface HasUserGroupIds
12+
{
13+
/**
14+
* @return int[]
15+
*/
16+
public function userGroupIds(): array;
17+
}

app/Contracts/Http/Requests/RequestAttribute.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class RequestAttribute
1414

1515
public const USER_ID_ATTRIBUTE = 'user_id';
1616
public const USER_IDS_ATTRIBUTE = 'user_ids';
17+
public const USER_GROUP_IDS_ATTRIBUTE = 'group_ids';
1718

1819
public const GROUP_ID = 'group_id';
1920
public const NAME_ATTRIBUTE = 'name';

app/Http/Controllers/Gallery/SharingController.php

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,35 @@ class SharingController extends Controller
4646
public function create(AddSharingRequest $request, Share $share): array
4747
{
4848
// delete any already created.
49-
AccessPermission::whereIn('user_id', $request->userIds())
50-
->whereIn('base_album_id', $request->albumIds())
49+
AccessPermission::whereIn('base_album_id', $request->albumIds())
50+
->where(fn ($q) => $q->whereIn('user_id', $request->userIds())
51+
->orWhereIn(APC::USER_GROUP_ID, $request->userGroupIds()))
5152
->delete();
5253

5354
$access_permissions = [];
55+
5456
// Not optimal, but this is barely used, so who cares.
5557
// A better approach would be to do a massive insert in a single SQL query from the cross product.
56-
foreach ($request->userIds() as $user_id) {
57-
foreach ($request->albumIds() as $album_id) {
58-
$access_permissions[] = $share->do($request->permResource(), $user_id, $album_id);
58+
foreach ($request->albumIds() as $album_id) {
59+
foreach ($request->userIds() as $user_id) {
60+
// Create a new sharing permission for each user and album combination.
61+
// This is not optimal, but it is simple and works.
62+
// A better approach would be to do a massive insert in a single SQL query from the cross product.
63+
$access_permissions[] = $share->do(
64+
access_permission_resource: $request->permResource(),
65+
user_id: $user_id,
66+
base_album_id: $album_id
67+
);
68+
}
69+
foreach ($request->userGroupIds() as $user_group_id) {
70+
// Create a new sharing permission for each user group and album combination.
71+
// This is not optimal, but it is simple and works.
72+
// A better approach would be to do a massive insert in a single SQL query from the cross product.
73+
$access_permissions[] = $share->do(
74+
access_permission_resource: $request->permResource(),
75+
user_group_id: $user_group_id,
76+
base_album_id: $album_id
77+
);
5978
}
6079
}
6180

@@ -92,9 +111,10 @@ public function edit(EditSharingRequest $request): AccessPermissionResource
92111
*/
93112
public function list(ListSharingRequest $request): Collection
94113
{
95-
$query = AccessPermission::with(['album', 'user']);
96-
$query = $query->whereNotNull(APC::USER_ID);
114+
$query = AccessPermission::with(['album', 'user', 'user_group']);
97115
$query = $query->where(APC::BASE_ALBUM_ID, '=', $request->album()->id);
116+
$query = $query->where(fn ($q) => $q->whereNotNull(APC::USER_ID)
117+
->orWhereNotNull(APC::USER_GROUP_ID));
98118

99119
return AccessPermissionResource::collect($query->get());
100120
}
@@ -108,12 +128,14 @@ public function list(ListSharingRequest $request): Collection
108128
*/
109129
public function listAll(ListAllSharingRequest $request): Collection
110130
{
111-
$query = AccessPermission::with(['album', 'user']);
131+
$query = AccessPermission::with(['album', 'user', 'user_group']);
112132
$query = $query->when(
113133
!Auth::user()->may_administrate,
114134
fn ($q) => $q->whereIn('base_album_id', BaseAlbumImpl::select('id')
115-
->where('owner_id', '=', Auth::id())));
116-
$query = $query->whereNotNull('user_id');
135+
->where('owner_id', '=', Auth::id()))
136+
);
137+
$query = $query->whereNotNull(APC::USER_ID);
138+
$query = $query->orWhereNotNull(APC::USER_GROUP_ID);
117139
$query = $query->orderBy('base_album_id', 'asc');
118140

119141
return AccessPermissionResource::collect($query->get());
@@ -134,10 +156,12 @@ public function listAlbums(ListAllSharingRequest $request, ListAlbums $list_albu
134156
$owner_id = $user->id;
135157
}
136158

137-
return TargetAlbumResource::collect($list_albums->do(
138-
albums_filtering: resolve(Collection::class),
139-
parent_id: null,
140-
owner_id: $owner_id)
159+
return TargetAlbumResource::collect(
160+
$list_albums->do(
161+
albums_filtering: resolve(Collection::class),
162+
parent_id: null,
163+
owner_id: $owner_id
164+
)
141165
);
142166
}
143167

@@ -173,4 +197,4 @@ public function propagate(PropagateSharingRequest $request, Propagate $propagate
173197
$propagate->update($album);
174198
}
175199
}
176-
}
200+
}

app/Http/Requests/Sharing/AddSharingRequest.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
use App\Http\Requests\BaseApiRequest;
1717
use App\Http\Requests\Traits\HasAccessPermissionResourceTrait;
1818
use App\Http\Requests\Traits\HasAlbumIdsTrait;
19+
use App\Http\Requests\Traits\HasUserGroupIdsTrait;
1920
use App\Http\Requests\Traits\HasUserIdsTrait;
2021
use App\Http\Resources\Models\AccessPermissionResource;
2122
use App\Policies\AlbumPolicy;
2223
use App\Rules\IntegerIDRule;
2324
use App\Rules\RandomIDRule;
2425
use Illuminate\Support\Facades\Gate;
26+
use Illuminate\Validation\ValidationException;
2527

2628
/**
2729
* Represents a request for setting the shares of specific albums.
@@ -32,6 +34,7 @@ class AddSharingRequest extends BaseApiRequest implements HasAlbumIds, HasUserId
3234
{
3335
use HasAlbumIdsTrait;
3436
use HasUserIdsTrait;
37+
use HasUserGroupIdsTrait;
3538
use HasAccessPermissionResourceTrait;
3639

3740
/**
@@ -50,8 +53,10 @@ public function rules(): array
5053
return [
5154
RequestAttribute::ALBUM_IDS_ATTRIBUTE => 'required|array|min:1',
5255
RequestAttribute::ALBUM_IDS_ATTRIBUTE . '.*' => ['required', new RandomIDRule(false)],
53-
RequestAttribute::USER_IDS_ATTRIBUTE => 'required|array|min:1',
56+
RequestAttribute::USER_IDS_ATTRIBUTE => 'present|array',
5457
RequestAttribute::USER_IDS_ATTRIBUTE . '.*' => ['required', new IntegerIDRule(false)],
58+
RequestAttribute::USER_GROUP_IDS_ATTRIBUTE => 'present|array',
59+
RequestAttribute::USER_GROUP_IDS_ATTRIBUTE . '.*' => ['required', new IntegerIDRule(false)],
5560
RequestAttribute::GRANTS_DOWNLOAD_ATTRIBUTE => ['required', 'boolean'],
5661
RequestAttribute::GRANTS_FULL_PHOTO_ACCESS_ATTRIBUTE => ['required', 'boolean'],
5762
RequestAttribute::GRANTS_UPLOAD_ATTRIBUTE => ['required', 'boolean'],
@@ -67,6 +72,13 @@ protected function processValidatedValues(array $values, array $files): void
6772
{
6873
$this->album_ids = $values[RequestAttribute::ALBUM_IDS_ATTRIBUTE];
6974
$this->user_ids = $values[RequestAttribute::USER_IDS_ATTRIBUTE];
75+
$this->user_group_ids = $values[RequestAttribute::USER_GROUP_IDS_ATTRIBUTE];
76+
77+
if ($this->user_ids === [] && $this->user_group_ids === []) {
78+
// If no users or groups are specified, we do not create a permission.
79+
throw new ValidationException('You must specify at least one user or group to share with.', 422);
80+
}
81+
7082
$this->perm_resource = new AccessPermissionResource(
7183
grants_edit: static::toBoolean($values[RequestAttribute::GRANTS_EDIT_ATTRIBUTE]),
7284
grants_delete: static::toBoolean($values[RequestAttribute::GRANTS_DELETE_ATTRIBUTE]),

app/Http/Requests/Sharing/EditSharingRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected function processValidatedValues(array $values, array $files): void
6464
{
6565
/** @var int $id */
6666
$id = $values[RequestAttribute::PERMISSION_ID];
67-
$this->perm = AccessPermission::with(['album', 'user'])->findOrFail($id);
67+
$this->perm = AccessPermission::with(['album', 'user', 'user_group'])->findOrFail($id);
6868

6969
$this->perm_resource = new AccessPermissionResource(
7070
grants_edit: static::toBoolean($values[RequestAttribute::GRANTS_EDIT_ATTRIBUTE]),
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2025 LycheeOrg.
7+
*/
8+
9+
namespace App\Http\Requests\Traits;
10+
11+
trait HasUserGroupIdsTrait
12+
{
13+
/**
14+
* @var array<int,int>
15+
*/
16+
protected array $user_group_ids = [];
17+
18+
/**
19+
* @return array<int,int>
20+
*/
21+
public function userGroupIds(): array
22+
{
23+
return $this->user_group_ids;
24+
}
25+
}

app/Http/Resources/Models/AccessPermissionResource.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class AccessPermissionResource extends Data
1818
public function __construct(
1919
public ?int $id = null,
2020
public ?int $user_id = null,
21+
public ?int $user_group_id = null,
2122
public ?string $username = null,
23+
public ?string $user_group_name = null,
2224
public ?string $album_title = null,
2325
public ?string $album_id = null,
2426
public bool $grants_full_photo_access = false,
@@ -33,8 +35,10 @@ public static function fromModel(AccessPermission $access_permission): AccessPer
3335
{
3436
return new AccessPermissionResource(
3537
id: $access_permission->id,
36-
user_id: $access_permission->user_id,
37-
username: $access_permission->user->name,
38+
user_id: $access_permission->user?->id,
39+
username: $access_permission->user?->name,
40+
user_group_id: $access_permission->user_group?->id,
41+
user_group_name: $access_permission->user_group?->name,
3842
album_title: $access_permission->album->title,
3943
album_id: $access_permission->base_album_id,
4044
grants_full_photo_access: $access_permission->grants_full_photo_access,

app/Models/AccessPermission.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class AccessPermission extends Model
7373
'created_at' => 'datetime',
7474
'updated_at' => 'datetime',
7575
APC::USER_ID => 'integer',
76+
APC::USER_GROUP_ID => 'integer',
7677
APC::IS_LINK_REQUIRED => 'boolean',
7778
APC::GRANTS_FULL_PHOTO_ACCESS => 'boolean',
7879
APC::GRANTS_DOWNLOAD => 'boolean',
@@ -86,6 +87,7 @@ class AccessPermission extends Model
8687
*/
8788
protected $fillable = [
8889
APC::USER_ID,
90+
APC::USER_GROUP_ID,
8991
APC::BASE_ALBUM_ID,
9092
APC::IS_LINK_REQUIRED,
9193
APC::GRANTS_FULL_PHOTO_ACCESS,

app/Models/User.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ class User extends Authenticatable implements WebAuthnAuthenticatable
120120

121121
protected $hidden = [];
122122

123+
/**
124+
* We always want to load the user groups when loading a user.
125+
* So that we can use the groups to determine the permissions without having to do intersection in the db.
126+
*
127+
* Furthermore, that way it is also provided when using Auth::user()
128+
*
129+
* @var list<string>
130+
*/
123131
protected $with = [
124132
'user_groups',
125133
];

resources/js/lychee.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,9 @@ declare namespace App.Http.Resources.Models {
334334
export type AccessPermissionResource = {
335335
id: number | null;
336336
user_id: number | null;
337+
user_group_id: number | null;
337338
username: string | null;
339+
user_group_name: string | null;
338340
album_title: string | null;
339341
album_id: string | null;
340342
grants_full_photo_access: boolean;

tests/Feature_v2/Album/SharingTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public function testUserGet(): void
8888

8989
$response = $this->actingAs($this->userMayUpload2)->postJson('Sharing', [
9090
'user_ids' => [$this->userMayUpload1->id],
91+
'group_ids' => [],
9192
'album_ids' => [$this->album2->id],
9293
'grants_edit' => true,
9394
'grants_delete' => true,
@@ -141,8 +142,8 @@ public function testUpdate(): void
141142
'shall_override' => false,
142143
]);
143144
$this->assertNoContent($response);
144-
self::assertEquals(1, AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->count());
145-
$perm = AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->first();
145+
self::assertEquals(2, AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->count());
146+
$perm = AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->whereNull(APC::USER_GROUP_ID)->first();
146147

147148
// Update the permission with false
148149
$response = $this->actingAs($this->userMayUpload1)->patchJson('Sharing', [
@@ -156,7 +157,7 @@ public function testUpdate(): void
156157
$this->assertOk($response);
157158

158159
// Verify the permission
159-
$perm = AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->first();
160+
$perm = AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->whereNull(APC::USER_GROUP_ID)->first();
160161
self::assertFalse($perm->grants_edit);
161162
self::assertFalse($perm->grants_delete);
162163
self::assertFalse($perm->grants_download);
@@ -170,10 +171,10 @@ public function testUpdate(): void
170171
]);
171172
$this->assertNoContent($response);
172173
// Verify the count is still 1.
173-
self::assertEquals(1, AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->count());
174+
self::assertEquals(2, AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->count());
174175

175176
// Verify the permission
176-
$perm = AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->first();
177+
$perm = AccessPermission::where(APC::BASE_ALBUM_ID, '=', $this->subAlbum1->id)->whereNull(APC::USER_GROUP_ID)->first();
177178
self::assertTrue($perm->grants_edit);
178179
self::assertTrue($perm->grants_delete);
179180
self::assertTrue($perm->grants_download);
@@ -186,6 +187,7 @@ public function testOverride(): void
186187
// Set up the permission in subSlbum
187188
$response = $this->actingAs($this->userMayUpload1)->postJson('Sharing', [
188189
'user_ids' => [$this->userLocked->id],
190+
'group_ids' => [],
189191
'album_ids' => [$this->subAlbum1->id],
190192
'grants_edit' => true,
191193
'grants_delete' => true,
@@ -198,6 +200,7 @@ public function testOverride(): void
198200

199201
$response = $this->actingAs($this->userMayUpload1)->postJson('Sharing', [
200202
'user_ids' => [$this->userNoUpload->id],
203+
'group_ids' => [],
201204
'album_ids' => [$this->album1->id],
202205
'grants_edit' => true,
203206
'grants_delete' => true,

0 commit comments

Comments
 (0)