Compare commits
3
Commits
0098ec3a65
...
164008bef1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
164008bef1 | ||
|
|
4d5694dca6 | ||
|
|
a680f6363d |
@@ -137,22 +137,10 @@ public class PersonController(
|
||||
var visibility = personBulkDto.Data.Visibility;
|
||||
|
||||
if (cascade != null && visibility.HasValue) {
|
||||
var albums = cascade.ToAlbums || cascade.ToAssets
|
||||
? albumRepository.FindByPersonIds(personBulkDto.Ids).ToList()
|
||||
: [];
|
||||
|
||||
if (cascade.ToAlbums) {
|
||||
foreach (var album in albums) {
|
||||
album.Visibility = visibility.Value;
|
||||
album.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
result.AlbumsUpdated = albums.Count;
|
||||
}
|
||||
|
||||
if (cascade.ToAssets && albums.Count > 0) {
|
||||
result.AssetsUpdated = assetRepository.BulkSetVisibilityByAlbumIds(
|
||||
albums.Select(a => a.Id).ToList(), visibility.Value, uid, accessLevel);
|
||||
}
|
||||
if (cascade.ToAlbums)
|
||||
result.AlbumsUpdated = albumRepository.BulkSetVisibilityByPersonIds(personBulkDto.Ids, visibility.Value);
|
||||
if (cascade.ToAssets)
|
||||
result.AssetsUpdated = assetRepository.BulkSetVisibilityByPersonIds(personBulkDto.Ids, visibility.Value);
|
||||
}
|
||||
|
||||
personRepository.Save();
|
||||
|
||||
@@ -193,6 +193,14 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
public IEnumerable<Album> FindByPersonIds(IEnumerable<Guid> personIds) =>
|
||||
context.Albums.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value));
|
||||
|
||||
/// <inheritdoc />
|
||||
public int BulkSetVisibilityByPersonIds(IEnumerable<Guid> personIds, EVisibility visibility) =>
|
||||
context.Albums
|
||||
.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value))
|
||||
.ExecuteUpdate(s => s
|
||||
.SetProperty(a => a.Visibility, visibility)
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow));
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Album> FindBulk(IEnumerable<Guid> ids) => context.Albums.Where(a => ids.Contains(a.Id));
|
||||
|
||||
|
||||
@@ -114,6 +114,15 @@ public class AssetRepository(LactoseDbContext context, ILogger<AssetRepository>
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int BulkSetVisibilityByPersonIds(IEnumerable<Guid> personIds, EVisibility visibility) =>
|
||||
context.Assets
|
||||
.Where(a => a.DeletedAt == null)
|
||||
.Where(a => a.Albums!.Any(al => al.PersonOwnerId != null && personIds.Contains(al.PersonOwnerId.Value)))
|
||||
.ExecuteUpdate(s => s
|
||||
.SetProperty(a => a.Visibility, visibility)
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow));
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Asset> FindBulk(IEnumerable<Guid> ids) => context.Assets
|
||||
.AsSplitQuery()
|
||||
|
||||
@@ -69,6 +69,14 @@ public interface IAlbumRepository : IDisposable {
|
||||
/// <returns>A collection of albums associated with the specified people.</returns>
|
||||
public IEnumerable<Album> FindByPersonIds(IEnumerable<Guid> personIds);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk-updates the visibility of all albums owned by the specified people via a single SQL UPDATE.
|
||||
/// </summary>
|
||||
/// <param name="personIds">The IDs of the people.</param>
|
||||
/// <param name="visibility">The target visibility level.</param>
|
||||
/// <returns>The number of albums updated.</returns>
|
||||
public int BulkSetVisibilityByPersonIds(IEnumerable<Guid> personIds, EVisibility visibility);
|
||||
|
||||
/// <summary>
|
||||
/// Finds multiple albums by their IDs.
|
||||
/// </summary>
|
||||
|
||||
@@ -57,6 +57,15 @@ public interface IAssetRepository : IDisposable {
|
||||
/// <returns>The number of assets updated.</returns>
|
||||
public int BulkSetVisibilityByAlbumIds(IEnumerable<Guid> albumIds, EVisibility visibility, Guid? userId, EAccessLevel accessLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk-updates the visibility of non-deleted assets in any album owned by the specified people
|
||||
/// via a single SQL UPDATE.
|
||||
/// </summary>
|
||||
/// <param name="personIds">The IDs of the people whose album assets to update.</param>
|
||||
/// <param name="visibility">The target visibility level.</param>
|
||||
/// <returns>The number of assets updated.</returns>
|
||||
public int BulkSetVisibilityByPersonIds(IEnumerable<Guid> personIds, EVisibility visibility);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all assets created or updated between the given dates.
|
||||
/// </summary>
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
CascadeLabel="Also change visibility of all assets in these albums (skips deleted)"
|
||||
CascadeEnabled="@cascadeToAssets"
|
||||
CascadeEnabledChanged="@((bool v) => cascadeToAssets = v)"
|
||||
Error="@visibilityError"
|
||||
OnApply="ApplyAlbumVisibility"
|
||||
OnClose="CloseVisibilityModal" />
|
||||
}
|
||||
@@ -187,6 +188,7 @@
|
||||
List<MergeModal.MergeItem> mergeItems = [];
|
||||
bool cascadeToAssets;
|
||||
EVisibility selectedAlbumVisibility = EVisibility.Public;
|
||||
string? visibilityError;
|
||||
HashSet<Guid> selectedAlbumIds = [];
|
||||
bool albumsAllSelected => albumGridRef != null && selectedAlbumIds.Count == albumGridRef.GetCurrentAlbumIds().Count && selectedAlbumIds.Count > 0;
|
||||
string? searchQuery;
|
||||
@@ -263,6 +265,7 @@
|
||||
void OpenVisibilityModal() {
|
||||
showVisibilityModal = true;
|
||||
selectedAlbumVisibility = EVisibility.Public;
|
||||
visibilityError = null;
|
||||
}
|
||||
|
||||
void CloseVisibilityModal() {
|
||||
@@ -304,16 +307,21 @@
|
||||
|
||||
async Task ApplyAlbumVisibility() {
|
||||
if (selectedAlbumIds.Count == 0) return;
|
||||
await albumService.BulkUpdateAlbumsAsync(
|
||||
[.. selectedAlbumIds],
|
||||
new AlbumUpdateDto { Visibility = selectedAlbumVisibility },
|
||||
new CascadeOptions { ToAssets = cascadeToAssets }
|
||||
);
|
||||
showVisibilityModal = false;
|
||||
cascadeToAssets = false;
|
||||
selectMode = false;
|
||||
selectedAlbumIds.Clear();
|
||||
loadVersion++;
|
||||
visibilityError = null;
|
||||
try {
|
||||
await albumService.BulkUpdateAlbumsAsync(
|
||||
[.. selectedAlbumIds],
|
||||
new AlbumUpdateDto { Visibility = selectedAlbumVisibility },
|
||||
new CascadeOptions { ToAssets = cascadeToAssets }
|
||||
);
|
||||
showVisibilityModal = false;
|
||||
cascadeToAssets = false;
|
||||
selectMode = false;
|
||||
selectedAlbumIds.Clear();
|
||||
loadVersion++;
|
||||
} catch (Exception) {
|
||||
visibilityError = "The visibility update failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
void OpenCreateForm() {
|
||||
|
||||
@@ -277,6 +277,7 @@
|
||||
CascadeLabel="Also change visibility of all assets in these albums (skips deleted)"
|
||||
CascadeEnabled="@cascadeToAssets"
|
||||
CascadeEnabledChanged="@((bool v) => cascadeToAssets = v)"
|
||||
Error="@visibilityError"
|
||||
OnApply="ApplyAlbumVisibility"
|
||||
OnClose="CloseVisibilityModal" />
|
||||
}
|
||||
@@ -314,6 +315,7 @@
|
||||
Func<Task>? _pendingDeleteAction;
|
||||
bool cascadeToAssets;
|
||||
EVisibility selectedAlbumVisibility = EVisibility.Public;
|
||||
string? visibilityError;
|
||||
string viewMode = "masonry";
|
||||
bool showAlbumAssigner;
|
||||
List<AlbumPreviewDto>? unassignedAlbums;
|
||||
@@ -475,6 +477,7 @@
|
||||
void OpenVisibilityModal() {
|
||||
showAlbumVisibilityModal = true;
|
||||
selectedAlbumVisibility = EVisibility.Public;
|
||||
visibilityError = null;
|
||||
}
|
||||
|
||||
void CloseVisibilityModal() {
|
||||
@@ -484,16 +487,21 @@
|
||||
|
||||
async Task ApplyAlbumVisibility() {
|
||||
if (person == null || selectedAlbumIds.Count == 0) return;
|
||||
await albumService.BulkUpdateAlbumsAsync(
|
||||
[.. selectedAlbumIds],
|
||||
new AlbumUpdateDto { Visibility = selectedAlbumVisibility },
|
||||
new CascadeOptions { ToAssets = cascadeToAssets }
|
||||
);
|
||||
showAlbumVisibilityModal = false;
|
||||
cascadeToAssets = false;
|
||||
CancelSelectMode();
|
||||
await LoadPerson();
|
||||
albumLoadVersion++;
|
||||
visibilityError = null;
|
||||
try {
|
||||
await albumService.BulkUpdateAlbumsAsync(
|
||||
[.. selectedAlbumIds],
|
||||
new AlbumUpdateDto { Visibility = selectedAlbumVisibility },
|
||||
new CascadeOptions { ToAssets = cascadeToAssets }
|
||||
);
|
||||
showAlbumVisibilityModal = false;
|
||||
cascadeToAssets = false;
|
||||
CancelSelectMode();
|
||||
await LoadPerson();
|
||||
albumLoadVersion++;
|
||||
} catch (Exception) {
|
||||
visibilityError = "The visibility update failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
async Task ConfirmDelete() {
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
CascadeLabel2="Change visibility of all assets in every album (skips deleted)"
|
||||
CascadeEnabled2="@cascadeToAssets"
|
||||
CascadeEnabled2Changed="@((bool v) => cascadeToAssets = v)"
|
||||
Error="@visibilityError"
|
||||
OnApply="ApplyVisibility"
|
||||
OnClose="CloseVisibilityModal" />
|
||||
}
|
||||
@@ -159,6 +160,7 @@
|
||||
bool cascadeToAlbums;
|
||||
bool cascadeToAssets;
|
||||
EVisibility selectedVisibility = EVisibility.Public;
|
||||
string? visibilityError;
|
||||
HashSet<Guid> selectedCosplayerIds = [];
|
||||
string? searchQuery;
|
||||
string? sortBy;
|
||||
@@ -257,6 +259,7 @@
|
||||
void OpenVisibilityModal() {
|
||||
showVisibilityModal = true;
|
||||
selectedVisibility = EVisibility.Public;
|
||||
visibilityError = null;
|
||||
cascadeToAlbums = false;
|
||||
cascadeToAssets = false;
|
||||
}
|
||||
@@ -269,14 +272,19 @@
|
||||
|
||||
async Task ApplyVisibility() {
|
||||
if (selectedCosplayerIds.Count == 0) return;
|
||||
await personService.BulkUpdatePeopleAsync(
|
||||
[.. selectedCosplayerIds],
|
||||
new PersonUpdateDto { Visibility = selectedVisibility },
|
||||
new CascadeOptions { ToAlbums = cascadeToAlbums, ToAssets = cascadeToAssets }
|
||||
);
|
||||
showVisibilityModal = false;
|
||||
CancelSelectMode();
|
||||
loadVersion++;
|
||||
visibilityError = null;
|
||||
try {
|
||||
await personService.BulkUpdatePeopleAsync(
|
||||
[.. selectedCosplayerIds],
|
||||
new PersonUpdateDto { Visibility = selectedVisibility },
|
||||
new CascadeOptions { ToAlbums = cascadeToAlbums, ToAssets = cascadeToAssets }
|
||||
);
|
||||
showVisibilityModal = false;
|
||||
CancelSelectMode();
|
||||
loadVersion++;
|
||||
} catch (Exception) {
|
||||
visibilityError = "The visibility update failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
ItemLabel="asset(s)"
|
||||
Visibility="@_visibilitySelection"
|
||||
VisibilityChanged="@((EVisibility v) => _visibilitySelection = v)"
|
||||
Error="@_visibilityError"
|
||||
OnApply="ApplyVisibility"
|
||||
OnClose="() => _showVisibilityModal = false" />
|
||||
}
|
||||
@@ -441,6 +442,7 @@
|
||||
|
||||
private bool _showVisibilityModal;
|
||||
private EVisibility _visibilitySelection = EVisibility.Public;
|
||||
private string? _visibilityError;
|
||||
|
||||
private bool _showCreateAlbumModal;
|
||||
private string _newAlbumName = "";
|
||||
@@ -556,15 +558,25 @@
|
||||
|
||||
void OpenVisibilityModal() {
|
||||
_visibilitySelection = EVisibility.Public;
|
||||
_visibilityError = null;
|
||||
_showVisibilityModal = true;
|
||||
}
|
||||
|
||||
async Task ApplyVisibility() {
|
||||
if (selectedIds.Count == 0) return;
|
||||
await assetService.BulkUpdateAssetsAsync([.. selectedIds], new AssetUpdateDto { Visibility = _visibilitySelection });
|
||||
_showVisibilityModal = false;
|
||||
selectedIds.Clear();
|
||||
await LoadContent();
|
||||
_visibilityError = null;
|
||||
try {
|
||||
var ok = await assetService.BulkUpdateAssetsAsync([.. selectedIds], new AssetUpdateDto { Visibility = _visibilitySelection });
|
||||
if (!ok) {
|
||||
_visibilityError = "The visibility update failed. Please try again.";
|
||||
return;
|
||||
}
|
||||
_showVisibilityModal = false;
|
||||
selectedIds.Clear();
|
||||
await LoadContent();
|
||||
} catch (Exception) {
|
||||
_visibilityError = "The visibility update failed. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
void OpenCreateAlbumModal() {
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Error)) {
|
||||
<div class="alert alert-danger py-1 mb-0">@Error</div>
|
||||
}
|
||||
</div>
|
||||
</Body>
|
||||
<Footer>
|
||||
@@ -68,6 +71,7 @@
|
||||
[Parameter] public string? CascadeLabel2 { get; set; }
|
||||
[Parameter] public bool CascadeEnabled2 { get; set; }
|
||||
[Parameter] public EventCallback<bool> CascadeEnabled2Changed { get; set; }
|
||||
[Parameter] public string? Error { get; set; }
|
||||
|
||||
bool _isApplying;
|
||||
|
||||
|
||||
@@ -140,10 +140,11 @@ public sealed class AlbumService(
|
||||
/// <param name="ids">The album IDs to update.</param>
|
||||
/// <param name="dto">The shared update values.</param>
|
||||
/// <param name="cascade">Optional cascade options (e.g. cascade a visibility change to assets).</param>
|
||||
public async Task<bool> BulkUpdateAlbumsAsync(List<Guid> ids, AlbumUpdateDto dto, CascadeOptions? cascade = null) {
|
||||
/// <exception cref="HttpRequestException">Thrown when the request fails.</exception>
|
||||
public async Task BulkUpdateAlbumsAsync(List<Guid> ids, AlbumUpdateDto dto, CascadeOptions? cascade = null) {
|
||||
var payload = new BulkDto<AlbumUpdateDto> { Ids = ids, Data = dto, Cascade = cascade };
|
||||
var response = await Client.PostAsJsonAsync("/api/album", payload);
|
||||
return response.IsSuccessStatusCode;
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -116,10 +116,11 @@ public sealed class PersonService(
|
||||
/// <param name="ids">The person IDs to update.</param>
|
||||
/// <param name="dto">The shared update values.</param>
|
||||
/// <param name="cascade">Optional cascade options (e.g. cascade a visibility change to albums and assets).</param>
|
||||
public async Task<bool> BulkUpdatePeopleAsync(List<Guid> ids, PersonUpdateDto dto, CascadeOptions? cascade = null) {
|
||||
/// <exception cref="HttpRequestException">Thrown when the request fails.</exception>
|
||||
public async Task BulkUpdatePeopleAsync(List<Guid> ids, PersonUpdateDto dto, CascadeOptions? cascade = null) {
|
||||
var payload = new BulkDto<PersonUpdateDto> { Ids = ids, Data = dto, Cascade = cascade };
|
||||
var response = await Client.PostAsJsonAsync("/api/person", payload);
|
||||
return response.IsSuccessStatusCode;
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user