feat(Lactose): convert AlbumRepository to async
This commit is contained in:
@@ -28,7 +28,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<AlbumPreviewDto> SearchQuery(string query, int page = 0, int pageSize = PagedParametersDto.MaxPageSize, string? sortBy = null, bool sortAsc = false, bool unassigned = false, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User, Guid? uploadedBy = null, Guid? personOwnerId = null) {
|
||||
public async Task<List<AlbumPreviewDto>> SearchQueryAsync(string query, int page, int pageSize, string? sortBy, bool sortAsc, bool unassigned, Guid userId, EAccessLevel accessLevel, Guid? uploadedBy, Guid? personOwnerId, CancellationToken cancellationToken) {
|
||||
IQueryable<Album> albumsQuery = context.Albums.AsNoTracking();
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
@@ -63,31 +63,31 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
};
|
||||
|
||||
var pageOffset = page * pageSize;
|
||||
var albumIds = ordered
|
||||
var albumIds = await ordered
|
||||
.Skip(pageOffset)
|
||||
.Take(pageSize)
|
||||
.Select(a => a.Id)
|
||||
.ToList();
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (albumIds.Count == 0)
|
||||
return [];
|
||||
|
||||
var pagedAlbums = context.Albums
|
||||
var pagedAlbums = await context.Albums
|
||||
.AsNoTracking()
|
||||
.Include(a => a.PersonOwner)
|
||||
.Include(a => a.CoverAsset)
|
||||
.Where(a => albumIds.Contains(a.Id))
|
||||
.ToList();
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// Compute visible asset count per album at the DB level
|
||||
Dictionary<Guid, int> assetCounts;
|
||||
if (accessLevel >= EAccessLevel.Curator) {
|
||||
assetCounts = context.Albums
|
||||
assetCounts = await context.Albums
|
||||
.Where(a => albumIds.Contains(a.Id))
|
||||
.Select(a => new { a.Id, Count = a.Assets!.Count() })
|
||||
.ToDictionary(x => x.Id, x => x.Count);
|
||||
.ToDictionaryAsync(x => x.Id, x => x.Count, cancellationToken);
|
||||
} else {
|
||||
assetCounts = context.Albums
|
||||
assetCounts = await context.Albums
|
||||
.Where(a => albumIds.Contains(a.Id))
|
||||
.Select(a => new {
|
||||
a.Id,
|
||||
@@ -97,7 +97,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
(asset.Visibility == EVisibility.Private && asset.UploadedBy == userId)
|
||||
))
|
||||
})
|
||||
.ToDictionary(x => x.Id, x => x.Count);
|
||||
.ToDictionaryAsync(x => x.Id, x => x.Count, cancellationToken);
|
||||
}
|
||||
|
||||
var dtos = pagedAlbums.Select(a => {
|
||||
@@ -109,9 +109,9 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
var orderMap = albumIds.Select((id, i) => (id, i)).ToDictionary(x => x.id, x => x.i);
|
||||
return [.. dtos.OrderBy(d => orderMap.GetValueOrDefault(d.Id))];
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save() => context.SaveChanges();
|
||||
public Task SaveAsync(CancellationToken cancellationToken) => context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TResult?> ExecuteInTransactionAsync<TResult>(Func<Task<TResult?>> operation) {
|
||||
@@ -122,20 +122,21 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Album? Find(Guid id) => context.Albums.FirstOrDefault(a => a.Id == id);
|
||||
public Task<Album?> FindAsync(Guid id, CancellationToken cancellationToken) =>
|
||||
context.Albums.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Album? FindWithAssets(Guid id) => context.Albums
|
||||
public Task<Album?> FindWithAssetsAsync(Guid id, CancellationToken cancellationToken) => context.Albums
|
||||
.AsSplitQuery()
|
||||
.Include(a => a.Assets)
|
||||
.FirstOrDefault(a => a.Id == id);
|
||||
.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Album? FindVisible(Guid id, Guid? userId, EAccessLevel accessLevel) {
|
||||
var album = context.Albums
|
||||
public async Task<Album?> FindVisibleAsync(Guid id, Guid? userId, EAccessLevel accessLevel, CancellationToken cancellationToken) {
|
||||
var album = await context.Albums
|
||||
.AsNoTracking()
|
||||
.Include(a => a.PersonOwner)
|
||||
.FirstOrDefault(a => a.Id == id);
|
||||
.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
|
||||
|
||||
if (album == null) return null;
|
||||
|
||||
@@ -143,8 +144,8 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
bool canSeeAlbum = accessLevel switch {
|
||||
< EAccessLevel.Maintainer => album.Visibility <= EVisibility.Protected,
|
||||
EAccessLevel.Maintainer when userId.HasValue => album.Visibility <= EVisibility.Protected
|
||||
|| (album.PersonOwnerId.HasValue && context.PersonMaintainers.Any(pm =>
|
||||
pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value)),
|
||||
|| (album.PersonOwnerId.HasValue && await context.PersonMaintainers.AnyAsync(pm =>
|
||||
pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value, cancellationToken)),
|
||||
EAccessLevel.Maintainer => album.Visibility <= EVisibility.Protected,
|
||||
_ => true
|
||||
};
|
||||
@@ -160,7 +161,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
EAccessLevel.Admin => assetsQuery,
|
||||
EAccessLevel.Curator => assetsQuery.Where(a => a.DeletedAt == null || a.UploadedBy == userId),
|
||||
EAccessLevel.Maintainer when userId.HasValue && album.PersonOwnerId.HasValue
|
||||
&& context.PersonMaintainers.Any(pm => pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value)
|
||||
&& await context.PersonMaintainers.AnyAsync(pm => pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value, cancellationToken)
|
||||
=> assetsQuery.Where(a => a.DeletedAt == null),
|
||||
_ => assetsQuery.Where(a => a.DeletedAt == null && (
|
||||
a.Visibility == EVisibility.Public ||
|
||||
@@ -169,7 +170,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
))
|
||||
};
|
||||
|
||||
album.VisibleAssetPreviews = assetsQuery
|
||||
album.VisibleAssetPreviews = await assetsQuery
|
||||
.OrderBy(a => a.OriginalFilename)
|
||||
.Select(a => new AlbumAssetPreviewDto {
|
||||
Id = a.Id,
|
||||
@@ -182,46 +183,48 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
Visibility = accessLevel >= EAccessLevel.Maintainer ? a.Visibility : null,
|
||||
DeletedAt = accessLevel >= EAccessLevel.Admin || a.UploadedBy == userId ? a.DeletedAt : null
|
||||
})
|
||||
.ToList();
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Album> FindByPerson(Guid personId) => context.Albums.Where(a => a.PersonOwnerId == personId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Album> FindByPersonIds(IEnumerable<Guid> personIds) =>
|
||||
context.Albums.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value));
|
||||
public Task<List<Album>> FindByPersonAsync(Guid personId, CancellationToken cancellationToken) =>
|
||||
context.Albums.Where(a => a.PersonOwnerId == personId).ToListAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public int BulkSetVisibilityByPersonIds(IEnumerable<Guid> personIds, EVisibility visibility) =>
|
||||
public Task<List<Album>> FindByPersonIdsAsync(IEnumerable<Guid> personIds, CancellationToken cancellationToken) =>
|
||||
context.Albums.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value)).ToListAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> BulkSetVisibilityByPersonIdsAsync(IEnumerable<Guid> personIds, EVisibility visibility, CancellationToken cancellationToken) =>
|
||||
context.Albums
|
||||
.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value))
|
||||
.ExecuteUpdate(s => s
|
||||
.ExecuteUpdateAsync(s => s
|
||||
.SetProperty(a => a.Visibility, visibility)
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow));
|
||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Album> FindBulk(IEnumerable<Guid> ids) => context.Albums.Where(a => ids.Contains(a.Id));
|
||||
public Task<List<Album>> FindBulkAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken) =>
|
||||
context.Albums.Where(a => ids.Contains(a.Id)).ToListAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Album> FindByDateRange(DateTime from, DateTime to)
|
||||
=> context.Albums.Where(x => x.CreatedAt >= from && x.UpdatedAt <= to);
|
||||
public Task<List<Album>> FindByDateRangeAsync(DateTime from, DateTime to, CancellationToken cancellationToken)
|
||||
=> context.Albums.Where(x => x.CreatedAt >= from && x.UpdatedAt <= to).ToListAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MergeAlbums(Guid destId, List<Guid> sourceIds) {
|
||||
var dest = context.Albums
|
||||
public async Task MergeAlbumsAsync(Guid destId, List<Guid> sourceIds, CancellationToken cancellationToken) {
|
||||
var dest = await context.Albums
|
||||
.AsSplitQuery()
|
||||
.Include(a => a.Assets)
|
||||
.FirstOrDefault(a => a.Id == destId);
|
||||
.FirstOrDefaultAsync(a => a.Id == destId, cancellationToken);
|
||||
if (dest == null) return;
|
||||
|
||||
var sources = context.Albums
|
||||
var sources = await context.Albums
|
||||
.AsSplitQuery()
|
||||
.Include(a => a.Assets)
|
||||
.Where(a => sourceIds.Contains(a.Id))
|
||||
.ToList();
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var existingAssetIds = dest.Assets?.Select(a => a.Id).ToHashSet() ?? [];
|
||||
|
||||
@@ -238,7 +241,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(Album album) => context.Albums.Remove(album);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => context.Dispose();
|
||||
}
|
||||
|
||||
@@ -10,41 +10,44 @@ namespace Lactose.Repositories;
|
||||
/// </summary>
|
||||
public interface IAlbumRepository : IDisposable {
|
||||
/// <summary>
|
||||
/// Inserts a new album into the database
|
||||
/// Inserts a new album into the database.
|
||||
/// </summary>
|
||||
/// <param name="album"></param>
|
||||
public void Insert(Album album);
|
||||
/// <param name="album">The album to insert.</param>
|
||||
void Insert(Album album);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an album in the database
|
||||
/// Updates an album in the database.
|
||||
/// </summary>
|
||||
/// <param name="album"></param>
|
||||
public void Update(Album album);
|
||||
/// <param name="album">The album to update.</param>
|
||||
void Update(Album album);
|
||||
|
||||
/// <summary>
|
||||
/// Updates multiple albums in the database.
|
||||
/// </summary>
|
||||
/// <param name="albums">The albums to update.</param>
|
||||
public void UpdateBulk(IEnumerable<Album> albums);
|
||||
void UpdateBulk(IEnumerable<Album> albums);
|
||||
|
||||
/// <summary>
|
||||
/// Saves changes to the database.
|
||||
/// </summary>
|
||||
public void Save();
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
Task SaveAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds an album by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the album.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>The album if found; otherwise, null.</returns>
|
||||
public Album? Find(Guid id);
|
||||
Task<Album?> FindAsync(Guid id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds an album by its ID with its assets loaded for collection replacement.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the album.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>The album with its asset collection loaded if found; otherwise, null.</returns>
|
||||
public Album? FindWithAssets(Guid id);
|
||||
Task<Album?> FindWithAssetsAsync(Guid id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds an album by its ID with assets filtered by the requesting user's access level.
|
||||
@@ -52,45 +55,51 @@ public interface IAlbumRepository : IDisposable {
|
||||
/// <param name="id">The ID of the album.</param>
|
||||
/// <param name="userId">The requesting user's ID for visibility-scoped asset filtering.</param>
|
||||
/// <param name="accessLevel">The requesting user's access level.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>The album if found; otherwise, null.</returns>
|
||||
public Album? FindVisible(Guid id, Guid? userId, EAccessLevel accessLevel);
|
||||
Task<Album?> FindVisibleAsync(Guid id, Guid? userId, EAccessLevel accessLevel, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds albums by the person's ID.
|
||||
/// </summary>
|
||||
/// <param name="personId">The ID of the person.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>A collection of albums associated with the specified person.</returns>
|
||||
public IEnumerable<Album> FindByPerson(Guid personId);
|
||||
Task<List<Album>> FindByPersonAsync(Guid personId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds albums owned by any of the specified people.
|
||||
/// </summary>
|
||||
/// <param name="personIds">The IDs of the people.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>A collection of albums associated with the specified people.</returns>
|
||||
public IEnumerable<Album> FindByPersonIds(IEnumerable<Guid> personIds);
|
||||
Task<List<Album>> FindByPersonIdsAsync(IEnumerable<Guid> personIds, CancellationToken cancellationToken);
|
||||
|
||||
/// <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>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>The number of albums updated.</returns>
|
||||
public int BulkSetVisibilityByPersonIds(IEnumerable<Guid> personIds, EVisibility visibility);
|
||||
Task<int> BulkSetVisibilityByPersonIdsAsync(IEnumerable<Guid> personIds, EVisibility visibility, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds multiple albums by their IDs.
|
||||
/// </summary>
|
||||
/// <param name="ids">The IDs of the albums.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>A collection of albums with the specified IDs.</returns>
|
||||
public IEnumerable<Album> FindBulk(IEnumerable<Guid> ids);
|
||||
Task<List<Album>> FindBulkAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Finds albums within a specified date range.
|
||||
/// </summary>
|
||||
/// <param name="from">The start date of the range.</param>
|
||||
/// <param name="to">The end date of the range.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>A collection of albums created or updated within the specified date range.</returns>
|
||||
public IEnumerable<Album> FindByDateRange(DateTime from, DateTime to);
|
||||
Task<List<Album>> FindByDateRangeAsync(DateTime from, DateTime to, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Merges multiple source albums into a destination album.
|
||||
@@ -98,13 +107,14 @@ public interface IAlbumRepository : IDisposable {
|
||||
/// </summary>
|
||||
/// <param name="destId">The ID of the destination album.</param>
|
||||
/// <param name="sourceIds">The IDs of the source albums to merge.</param>
|
||||
public void MergeAlbums(Guid destId, List<Guid> sourceIds);
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
Task MergeAlbumsAsync(Guid destId, List<Guid> sourceIds, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an album from the database.
|
||||
/// </summary>
|
||||
/// <param name="album">The album to remove.</param>
|
||||
public void Remove(Album album);
|
||||
void Remove(Album album);
|
||||
|
||||
/// <summary>
|
||||
/// Searches for albums based on a query string with optional sorting, filtering, and access-level scoping.
|
||||
@@ -119,8 +129,9 @@ public interface IAlbumRepository : IDisposable {
|
||||
/// <param name="accessLevel">The current user's access level. Regular users only see albums with visible assets.</param>
|
||||
/// <param name="uploadedBy">Optional filter for albums containing assets uploaded by a specific user.</param>
|
||||
/// <param name="personOwnerId">Optional filter for albums owned by a specific person.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <returns>A list of album previews matching the search query.</returns>
|
||||
public IEnumerable<AlbumPreviewDto> SearchQuery(string query, int page = 0, int pageSize = PagedParametersDto.MaxPageSize, string? sortBy = null, bool sortAsc = false, bool unassigned = false, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User, Guid? uploadedBy = null, Guid? personOwnerId = null);
|
||||
Task<List<AlbumPreviewDto>> SearchQueryAsync(string query, int page, int pageSize, string? sortBy, bool sortAsc, bool unassigned, Guid userId, EAccessLevel accessLevel, Guid? uploadedBy, Guid? personOwnerId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the given operation inside an explicit transaction on the shared database context,
|
||||
@@ -129,5 +140,5 @@ public interface IAlbumRepository : IDisposable {
|
||||
/// <typeparam name="TResult">The type returned by the operation.</typeparam>
|
||||
/// <param name="operation">The work to run inside the transaction.</param>
|
||||
/// <returns>The operation's result, or null if the operation returned null.</returns>
|
||||
public Task<TResult?> ExecuteInTransactionAsync<TResult>(Func<Task<TResult?>> operation);
|
||||
}
|
||||
Task<TResult?> ExecuteInTransactionAsync<TResult>(Func<Task<TResult?>> operation);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user