- BulkUpdate: Maintainer can update albums of persons they maintain - Delete: Maintainer can delete albums of persons they maintain - BulkDelete: same scoped filtering
287 lines
11 KiB
C#
287 lines
11 KiB
C#
using Butter.Dtos;
|
|
using Butter.Dtos.Album;
|
|
using Butter.Types;
|
|
using Lactose.Mapper;
|
|
using Lactose.Models;
|
|
using Lactose.Repositories;
|
|
using Lactose.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Lactose.Controllers;
|
|
|
|
/// <summary>
|
|
/// Manages albums — collections of assets.
|
|
/// </summary>
|
|
/// <param name="authService">Authentication service.</param>
|
|
/// <param name="albumRepository">Album repository.</param>
|
|
/// <param name="assetRepository">Asset repository.</param>
|
|
/// <param name="personRepository">Person repository.</param>
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("api/[controller]")]
|
|
public class AlbumController(
|
|
//TODO: Add logging
|
|
//ILogger<AlbumController> logger,
|
|
LactoseAuthService authService,
|
|
IAlbumRepository albumRepository,
|
|
IAssetRepository assetRepository,
|
|
IPersonRepository personRepository
|
|
) : ControllerBase {
|
|
/// <summary>
|
|
/// Gets an album by its ID with filtered assets based on access level.
|
|
/// </summary>
|
|
/// <param name="id">The album ID.</param>
|
|
/// <returns>The full album details with accessible assets, or 404 if not found.</returns>
|
|
[HttpGet("{id}")]
|
|
public ActionResult<AlbumFullDto> Get([FromRoute] Guid id) {
|
|
Guid? uid = authService.GetUserData(User)?.Id;
|
|
EAccessLevel accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
var album = albumRepository.Find(id);
|
|
|
|
// If the album does not exist, return a 404
|
|
if (album == null) return NotFound();
|
|
|
|
// If the album has no assets, return an empty list
|
|
if (album.Assets == null) return Ok(album.ToAlbumFullDto(accessLevel));
|
|
|
|
//Storage for assets that will be sent out;
|
|
List<Asset> assets = new();
|
|
|
|
switch (accessLevel) {
|
|
case EAccessLevel.User:
|
|
// Public assets, protected assets for authenticated users, or user's own private assets
|
|
assets.AddRange(album.Assets.Where(asset => asset.DeletedAt == null && (
|
|
asset.Visibility == EVisibility.Public ||
|
|
(asset.Visibility == EVisibility.Protected && uid.HasValue) ||
|
|
(asset.Visibility == EVisibility.Private && asset.UploadedBy == uid)
|
|
)));
|
|
break;
|
|
case EAccessLevel.Maintainer: {
|
|
bool isMaintainer = uid.HasValue && album.PersonOwnerId.HasValue
|
|
&& personRepository.IsMaintainerOf(uid.Value, album.PersonOwnerId.Value);
|
|
if (isMaintainer) {
|
|
assets.AddRange(album.Assets.Where(asset => asset.DeletedAt == null));
|
|
} else {
|
|
assets.AddRange(album.Assets.Where(asset => asset.DeletedAt == null && (
|
|
asset.Visibility == EVisibility.Public ||
|
|
(asset.Visibility == EVisibility.Protected && uid.HasValue) ||
|
|
(asset.Visibility == EVisibility.Private && asset.UploadedBy == uid)
|
|
)));
|
|
}
|
|
break;
|
|
}
|
|
case EAccessLevel.Curator:
|
|
// all assets minus deleted
|
|
assets.AddRange(album.Assets.Where(asset => asset.DeletedAt == null));
|
|
break;
|
|
case EAccessLevel.Admin:
|
|
// All assets
|
|
assets.AddRange(album.Assets);
|
|
break;
|
|
}
|
|
|
|
// changes the retrieved album to the new filtered
|
|
album.Assets = assets.OrderBy(a => a.OriginalFilename).ToList();
|
|
return Ok(album.ToAlbumFullDto(accessLevel));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches albums with pagination, optional search term, sort options, and optional unassigned filter.
|
|
/// </summary>
|
|
/// <param name="pagingOptions">Pagination, search, sort, and filter parameters.</param>
|
|
/// <returns>A list of album previews accessible to the user.</returns>
|
|
[HttpGet]
|
|
public ActionResult<List<AlbumPreviewDto>> Search([FromQuery] AlbumSearchParametersDto pagingOptions) {
|
|
var uid = authService.GetUserData(User)?.Id;
|
|
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
if(pagingOptions.Page < 0 || pagingOptions.PageSize < 1 || pagingOptions.PageSize > PagedParametersDto.MaxPageSize) return BadRequest();
|
|
|
|
var albums = albumRepository.SearchQuery(
|
|
pagingOptions.Search ?? string.Empty,
|
|
pagingOptions.Page,
|
|
pagingOptions.PageSize,
|
|
pagingOptions.SortBy,
|
|
pagingOptions.SortAsc,
|
|
pagingOptions.Unassigned,
|
|
uid ?? default,
|
|
accessLevel,
|
|
pagingOptions.UploadedBy
|
|
).ToList();
|
|
|
|
return Ok(albums);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing album.
|
|
/// </summary>
|
|
/// <param name="id">The album ID.</param>
|
|
/// <param name="albumDto">The album update data.</param>
|
|
/// <returns>200 on success, 403 if forbidden, 404 if not found.</returns>
|
|
[HttpPost("{id}")]
|
|
[Authorize(Roles = "Maintainer,Curator,Admin")]
|
|
public ActionResult Update([FromRoute] Guid id, [FromBody] AlbumUpdateDto albumDto) {
|
|
Guid? uid = authService.GetUserData(User)?.Id;
|
|
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
var album = albumRepository.Find(id);
|
|
|
|
// If the album does not exist, return a 404
|
|
if (album == null) return NotFound();
|
|
|
|
// Maintainer scope check — must maintain the album's person
|
|
if (accessLevel == EAccessLevel.Maintainer) {
|
|
if (album.PersonOwnerId == null || !personRepository.IsMaintainerOf(uid!.Value, album.PersonOwnerId.Value))
|
|
return Forbid();
|
|
} else if (accessLevel < EAccessLevel.Curator) {
|
|
return Forbid();
|
|
}
|
|
|
|
// Update album properties
|
|
album.Title = string.IsNullOrEmpty(albumDto.Name) ? album.Title : albumDto.Name;
|
|
album.UpdatedAt = DateTime.UtcNow;
|
|
album.PersonOwnerId = albumDto.RemovePerson ? null : albumDto.Person ?? album.PersonOwnerId;
|
|
album.Visibility = albumDto.Visibility ?? album.Visibility;
|
|
album.Assets = albumDto.Assets != null ? assetRepository.FindBulk(albumDto.Assets).ToList() : album.Assets;
|
|
album.CoverAssetId = albumDto.CoverAssetId ?? album.CoverAssetId;
|
|
|
|
// Save changes
|
|
albumRepository.Update(album);
|
|
albumRepository.Save();
|
|
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates multiple albums in a bulk operation.
|
|
/// </summary>
|
|
/// <param name="bulkDto">The bulk update data with album IDs and shared values.</param>
|
|
/// <returns>200 on success, 403 if forbidden.</returns>
|
|
[HttpPost]
|
|
[Authorize(Roles = "Maintainer,Curator,Admin")]
|
|
public ActionResult BulkUpdate([FromBody] BulkDto<AlbumUpdateDto> bulkDto) {
|
|
var uid = authService.GetUserData(User)?.Id;
|
|
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
var albums = albumRepository.FindBulk(bulkDto.Ids).ToList();
|
|
|
|
switch (accessLevel) {
|
|
case EAccessLevel.User:
|
|
// Users can't update albums
|
|
return Forbid();
|
|
case EAccessLevel.Maintainer:
|
|
// Maintainers can only update albums of persons they maintain
|
|
albums = albums.Where(a => a.PersonOwnerId != null && personRepository.IsMaintainerOf(uid!.Value, a.PersonOwnerId.Value)).ToList();
|
|
if (albums.Count == 0) return Forbid();
|
|
break;
|
|
case EAccessLevel.Curator:
|
|
// Curators can update any album
|
|
break;
|
|
case EAccessLevel.Admin:
|
|
// Admins can update any album
|
|
break;
|
|
}
|
|
|
|
foreach (var album in albums) {
|
|
album.Title = bulkDto.Data.Name ?? album.Title;
|
|
album.UpdatedAt = DateTime.UtcNow;
|
|
album.PersonOwnerId = bulkDto.Data.Person ?? album.PersonOwnerId;
|
|
album.Visibility = bulkDto.Data.Visibility ?? album.Visibility;
|
|
album.Assets = bulkDto.Data.Assets != null ? assetRepository.FindBulk(bulkDto.Data.Assets).ToList() : album.Assets;
|
|
album.CoverAssetId = bulkDto.Data.CoverAssetId ?? album.CoverAssetId;
|
|
}
|
|
|
|
albumRepository.UpdateBulk(albums);
|
|
albumRepository.Save();
|
|
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new album.
|
|
/// </summary>
|
|
/// <param name="albumDto">The album creation data.</param>
|
|
/// <returns>200 on success.</returns>
|
|
[HttpPut]
|
|
[Authorize(Roles = "Curator,Admin")]
|
|
public ActionResult Create([FromBody] AlbumCreateDto albumDto) {
|
|
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
var assets = albumDto.Assets != null ? assetRepository.FindBulk(albumDto.Assets).ToList() : new List<Asset>();
|
|
|
|
var album = new Album {
|
|
Id = Guid.NewGuid(),
|
|
Title = albumDto.Name,
|
|
CreatedAt = DateTime.UtcNow,
|
|
PersonOwnerId = albumDto.Person ?? null,
|
|
Visibility = albumDto.Visibility,
|
|
CoverAssetId = albumDto.CoverAssetId ?? assets.OrderBy(a => a.OriginalFilename).FirstOrDefault()?.Id,
|
|
Assets = assets
|
|
};
|
|
|
|
albumRepository.Insert(album);
|
|
albumRepository.Save();
|
|
|
|
return Ok(album.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes multiple albums (soft delete).
|
|
/// </summary>
|
|
/// <param name="albumIds">The list of album IDs to remove.</param>
|
|
/// <returns>200 on success, 403 if forbidden.</returns>
|
|
[HttpDelete]
|
|
[Authorize(Roles = "Maintainer,Curator,Admin")]
|
|
public ActionResult BulkDelete([FromBody] List<Guid> albumIds) {
|
|
var uid = authService.GetUserData(User)?.Id;
|
|
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
var albums = albumRepository.FindBulk(albumIds).ToList();
|
|
|
|
// Filter by access level
|
|
if (accessLevel == EAccessLevel.Maintainer) {
|
|
albums = albums.Where(a => a.PersonOwnerId != null && personRepository.IsMaintainerOf(uid!.Value, a.PersonOwnerId.Value)).ToList();
|
|
} else if (accessLevel < EAccessLevel.Curator) {
|
|
return Forbid();
|
|
}
|
|
|
|
if (albums.Count == 0) return Forbid();
|
|
|
|
albums.ForEach(albumRepository.Remove);
|
|
albumRepository.Save();
|
|
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an album by its ID (soft delete).
|
|
/// </summary>
|
|
/// <param name="id">The album ID.</param>
|
|
/// <returns>200 on success, 404 if not found, 403 if forbidden.</returns>
|
|
[HttpDelete("{id}")]
|
|
[Authorize(Roles = "Maintainer,Curator,Admin")]
|
|
public ActionResult Delete(Guid id) {
|
|
var uid = authService.GetUserData(User)?.Id;
|
|
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
|
|
|
var album = albumRepository.Find(id);
|
|
|
|
// If the album does not exist, return a 404
|
|
if (album == null) return NotFound();
|
|
|
|
if (accessLevel == EAccessLevel.Maintainer) {
|
|
if (album.PersonOwnerId == null || !personRepository.IsMaintainerOf(uid!.Value, album.PersonOwnerId.Value))
|
|
return Forbid();
|
|
} else if (accessLevel < EAccessLevel.Curator) {
|
|
return Forbid();
|
|
}
|
|
|
|
albumRepository.Remove(album);
|
|
albumRepository.Save();
|
|
|
|
return Ok();
|
|
}
|
|
}
|