Skip to content

Commit a86f747

Browse files
committed
progress
1 parent 890b9e6 commit a86f747

10 files changed

Lines changed: 183 additions & 95 deletions

File tree

app/Actions/Album/Merge.php

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88

99
namespace App\Actions\Album;
1010

11+
use App\Constants\PhotoAlbum as PA;
1112
use App\Exceptions\Internal\QueryBuilderException;
1213
use App\Exceptions\ModelDBException;
1314
use App\Models\Album;
1415
use App\Models\Photo;
1516
use Illuminate\Database\Eloquent\ModelNotFoundException;
1617
use Illuminate\Support\Collection;
18+
use Illuminate\Support\Facades\DB;
1719

1820
class Merge
1921
{
@@ -27,10 +29,28 @@ class Merge
2729
*/
2830
public function do(Album $target_album, Collection $albums): void
2931
{
30-
// Merge photos of source albums into target
31-
Photo::query()
32-
->whereIn('album_id', $albums->pluck('id'))
33-
->update(['album_id' => $target_album->id]);
32+
$origin_ids = $albums->pluck('id')->all();
33+
34+
// Select all photos ids of the source albums
35+
$photos_ids = DB::table(PA::PHOTO_ALBUM)
36+
->whereIn(PA::ALBUM_ID, $origin_ids)
37+
->pluck(PA::PHOTO_ID)->all();
38+
39+
// Delete the existing links at destination (avoid duplicates key contraint)
40+
DB::table(PA::PHOTO_ALBUM)
41+
->whereIn(PA::PHOTO_ID, $photos_ids)
42+
->where(PA::ALBUM_ID, '=', $target_album->id)
43+
->delete();
44+
45+
// Insert the new links
46+
DB::table(PA::PHOTO_ALBUM)
47+
->insert(array_map(fn (string $id) => ['photo_id' => $id, 'album_id' => $target_album->id], $photos_ids));
48+
49+
// Delete the existing links from the origins
50+
DB::table(PA::PHOTO_ALBUM)
51+
->whereIn(PA::PHOTO_ID, $photos_ids)
52+
->whereIn(PA::ALBUM_ID, $origin_ids)
53+
->delete();
3454

3555
// Merge sub-albums of source albums into target
3656
/** @var Album $album */

app/Actions/Photo/Delete.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ private function collectSizeVariantPathsByAlbumID(array $album_ids): void
161161
$size_variants = SizeVariant::query()
162162
->from('size_variants as sv')
163163
->select(['sv.short_path', 'sv.storage_disk'])
164-
->leftJoin(PA::PHOTO_ALBUM, PA::PHOTO_ID, '=', 'p.id')
164+
->leftJoin(PA::PHOTO_ALBUM, PA::PHOTO_ID, '=', 'sv.photo_id')
165165
->join('photos as p', 'p.id', '=', 'sv.photo_id')
166166
->joinSub(DB::table('photos')->leftJoin(PA::PHOTO_ALBUM, 'photos.id', '=', PA::PHOTO_ID)->select('id', 'checksum', 'album_id'), 'dup',
167167
function (JoinClause $join) use ($album_ids): void {

app/Actions/Photo/Duplicate.php

Lines changed: 0 additions & 37 deletions
This file was deleted.

app/Actions/Photo/Move.php

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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\Actions\Photo;
10+
11+
use App\Actions\User\Notify;
12+
use App\Constants\PhotoAlbum as PA;
13+
use App\Models\Album;
14+
use App\Models\Photo;
15+
use Illuminate\Support\Collection;
16+
use Illuminate\Support\Facades\DB;
17+
18+
class MoveOrDuplicate
19+
{
20+
/**
21+
* Move or Duplicates a set of photos.
22+
*
23+
* If $from_album = $to_album, this is a duplication.
24+
* If $from_album != $to_album, this is a move.
25+
*
26+
* @param Collection<int,Photo> $photos the source photos
27+
* @param Album $from_album the origin album; `null` means root album
28+
* @param Album $to_album the destination album; `null` means root album
29+
*
30+
* @return void
31+
*/
32+
public function do(Collection $photos, ?Album $from_album, ?Album $to_album): void
33+
{
34+
// Extract the photos Ids.
35+
$photos_ids = $photos->pluck('id')->all();
36+
37+
if ($from_album !== null) {
38+
// Delete the existing links.
39+
DB::table(PA::PHOTO_ALBUM)
40+
->whereIn(PA::PHOTO_ID, $photos_ids)
41+
->whereIn(PA::ALBUM_ID, $from_album->id)
42+
->delete();
43+
}
44+
45+
if ($to_album !== null) {
46+
// Delete the existing links at destination (avoid duplicates key contraint)
47+
// If $from === to this operation is not needed.
48+
DB::table(PA::PHOTO_ALBUM)->whereIn(PA::PHOTO_ID, $photos_ids)->where(PA::ALBUM_ID, '=', $to_album->id)->delete();
49+
50+
// Add the new links.
51+
DB::table(PA::PHOTO_ALBUM)->insert(array_map(fn (string $id) => ['photo_id' => $id, 'album_id' => $to_album->id], $photos_ids));
52+
}
53+
54+
// In case of move, we need to remove the header_id of said photos.
55+
if ($from_album !== null && $from_album->id !== $to_album?->id) {
56+
Album::query()
57+
->where('id', '=', $from_album->id)
58+
->whereIn('header_id', $photos->map(fn (Photo $p) => $p->id))->update(['header_id' => null]);
59+
}
60+
61+
$notify = new Notify();
62+
/** @var Photo $photo */
63+
foreach ($photos as $photo) {
64+
$notify->do($photo);
65+
}
66+
}
67+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
use App\Models\Album;
12+
13+
interface HasFromAlbum extends HasBaseAlbum
14+
{
15+
/**
16+
* @return Album|null
17+
*/
18+
public function from_album(): ?Album;
19+
}

app/Contracts/Http/Requests/RequestAttribute.php

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

2323
public const PARENT_ID_ATTRIBUTE = 'parent_id';
2424

25+
public const FROM_ID_ATTRIBUTE = 'from_id';
2526
public const ALBUM_ID_ATTRIBUTE = 'album_id';
2627
public const ALBUM_IDS_ATTRIBUTE = 'album_ids';
2728
public const ALBUM_DECORATION_ATTRIBUTE = 'album_decoration';

app/Http/Controllers/Gallery/PhotoController.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use App\Actions\Photo\Delete;
1313
use App\Actions\Photo\Duplicate;
1414
use App\Actions\Photo\Move;
15+
use App\Actions\Photo\MoveOrDuplicate;
1516
use App\Actions\Photo\Rotate;
1617
use App\Constants\FileSystem;
1718
use App\Contracts\Models\AbstractAlbum;
@@ -148,9 +149,13 @@ public function star(SetPhotosStarredRequest $request): void
148149
/**
149150
* Moves the photos to an album.
150151
*/
151-
public function move(MovePhotosRequest $request, Move $move): void
152+
public function move(MovePhotosRequest $request, MoveOrDuplicate $move): void
152153
{
153-
$move->do($request->photos(), $request->album());
154+
$move->do(
155+
photos: $request->photos(),
156+
from_album: $request->from_album(),
157+
to_album: $request->album()
158+
);
154159
}
155160

156161
/**
@@ -181,9 +186,9 @@ public function rotate(RotatePhotoRequest $request): PhotoResource
181186
* Copy a photos to an album.
182187
* Only the SQL entry is duplicated for space reason.
183188
*/
184-
public function copy(CopyPhotosRequest $request, Duplicate $duplicate): void
189+
public function copy(CopyPhotosRequest $request, MoveOrDuplicate $duplicate): void
185190
{
186-
$duplicate->do($request->photos(), $request->album());
191+
$duplicate->do($request->photos(), $request->album(), $request->album());
187192
}
188193

189194
/**

app/Http/Requests/Photo/MovePhotosRequest.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,53 @@
99
namespace App\Http\Requests\Photo;
1010

1111
use App\Contracts\Http\Requests\HasAlbum;
12+
use App\Contracts\Http\Requests\HasFromAlbum;
1213
use App\Contracts\Http\Requests\HasPhotos;
1314
use App\Contracts\Http\Requests\RequestAttribute;
15+
use App\Contracts\Models\AbstractAlbum;
1416
use App\Http\Requests\BaseApiRequest;
1517
use App\Http\Requests\Traits\Authorize\AuthorizeCanEditPhotosAlbumTrait;
1618
use App\Http\Requests\Traits\HasAlbumTrait;
19+
use App\Http\Requests\Traits\HasFromAlbumTrait;
1720
use App\Http\Requests\Traits\HasPhotosTrait;
1821
use App\Models\Album;
1922
use App\Models\Photo;
23+
use App\Policies\AlbumPolicy;
24+
use App\Policies\PhotoPolicy;
2025
use App\Rules\RandomIDRule;
26+
use Illuminate\Support\Facades\Gate;
2127

22-
class MovePhotosRequest extends BaseApiRequest implements HasPhotos, HasAlbum
28+
class MovePhotosRequest extends BaseApiRequest implements HasPhotos, HasAlbum, HasFromAlbum
2329
{
2430
use HasPhotosTrait;
2531
use HasAlbumTrait;
32+
use HasFromAlbumTrait;
2633
use AuthorizeCanEditPhotosAlbumTrait;
2734

35+
/**
36+
* {@inheritDoc}
37+
*/
38+
public function authorize(): bool
39+
{
40+
if (!Gate::check(AlbumPolicy::CAN_EDIT, [AbstractAlbum::class, $this->album])) {
41+
return false;
42+
}
43+
44+
if (!Gate::check(AlbumPolicy::CAN_EDIT, [AbstractAlbum::class, $this->from_album])) {
45+
return false;
46+
}
47+
48+
// TODO: refactor this check so it does not explode.
49+
/** @var Photo $photo */
50+
foreach ($this->photos as $photo) {
51+
if (!Gate::check(PhotoPolicy::CAN_EDIT, $photo)) {
52+
return false;
53+
}
54+
}
55+
56+
return true;
57+
}
58+
2859
/**
2960
* {@inheritDoc}
3061
*/
@@ -34,6 +65,7 @@ public function rules(): array
3465
RequestAttribute::PHOTO_IDS_ATTRIBUTE => 'required|array|min:1',
3566
RequestAttribute::PHOTO_IDS_ATTRIBUTE . '.*' => ['required', new RandomIDRule(false)],
3667
RequestAttribute::ALBUM_ID_ATTRIBUTE => ['present', new RandomIDRule(true)],
68+
RequestAttribute::FROM_ID_ATTRIBUTE => ['present', new RandomIDRule(true)],
3769
];
3870
}
3971

@@ -46,8 +78,13 @@ protected function processValidatedValues(array $values, array $files): void
4678
$photos_ids = $values[RequestAttribute::PHOTO_IDS_ATTRIBUTE];
4779
$this->photos = Photo::query()
4880
->findOrFail($photos_ids);
81+
4982
/** @var string|null */
5083
$target_album_id = $values[RequestAttribute::ALBUM_ID_ATTRIBUTE];
5184
$this->album = $target_album_id === null ? null : Album::query()->findOrFail($target_album_id);
85+
86+
/** @var string|null */
87+
$from_album_id = $values[RequestAttribute::ALBUM_ID_ATTRIBUTE];
88+
$this->from_album = $from_album_id === null ? null : Album::query()->findOrFail($from_album_id);
5289
}
5390
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
use App\Models\Album;
12+
13+
trait HasFromAlbumTrait
14+
{
15+
protected ?Album $from_album = null;
16+
17+
/**
18+
* @return Album|null
19+
*/
20+
public function from_album(): ?Album
21+
{
22+
return $this->from_album;
23+
}
24+
}

0 commit comments

Comments
 (0)