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;
///
/// Manages albums — collections of assets.
///
/// Authentication service.
/// Album repository.
/// Asset repository.
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class AlbumController(
//TODO: Add logging
//ILogger logger,
LactoseAuthService authService,
IAlbumRepository albumRepository,
IAssetRepository assetRepository
) : ControllerBase {
///
/// Gets an album by its ID with filtered assets based on access level.
///
/// The album ID.
/// The full album details with accessible assets, or 404 if not found.
[HttpGet("{id}")]
public ActionResult 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());
//Storage for assets that will be sent out;
List assets = new();
switch (accessLevel) {
case EAccessLevel.User:
// Only publicly shared assets or owned by user
assets.AddRange(album.Assets.Where(asset => (asset.IsPubliclyShared || asset.OwnerId == uid) && asset.DeletedAt == null));
// Add shared assets
assets.AddRange(album.Assets.Where(asset => asset.SharedWith!.Any(user => user.Id == uid) && asset.DeletedAt == null));
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());
}
///
/// Searches albums with pagination and optional search term.
///
/// Pagination and search parameters.
/// A list of album previews accessible to the user.
[HttpGet]
public ActionResult> Search([FromQuery] PagedSearchParametersDto pagingOptions) {
var uid = authService.GetUserData(User)?.Id;
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if(pagingOptions.Page < 0 || pagingOptions.PageSize < 0) return BadRequest();
var albums = albumRepository.SearchQuery(pagingOptions.Search ?? string.Empty, pagingOptions.Page, pagingOptions.PageSize).ToList();
switch (accessLevel) {
default:
case EAccessLevel.User:
List filteredAlbums;
// Only publicly shared albums or owned by user
filteredAlbums = albums.Where(album => album.Assets != null && (album.UserOwnerId == uid || album.Assets.Any(asset => asset.IsPubliclyShared && asset.DeletedAt == null))).ToList();
// Add shared albums
filteredAlbums.AddRange(albums.Where(album => album.Assets != null && album.Assets.Any(x => x.DeletedAt == null && x.SharedWith!.Any(user => user.Id == uid))));
return Ok(filteredAlbums.Select(x => x.ToAlbumPreviewDto()).ToList());
case EAccessLevel.Curator:
case EAccessLevel.Admin:
// All albums
return Ok(albums.Select(x => x.ToAlbumPreviewDto()).ToList());
}
}
///
/// Gets all albums that have no person assigned.
///
/// A list of unassigned album previews.
[HttpGet("unassigned")]
public ActionResult> GetUnassigned() {
var albums = albumRepository.FindUnassigned().ToList();
return Ok(albums.Select(x => x.ToAlbumPreviewDto()).ToList());
}
///
/// Updates an existing album.
///
/// The album ID.
/// The album update data.
/// 200 on success, 403 if forbidden, 404 if not found.
[HttpPost("{id}")]
[Authorize(Roles = "Admin, Curator")]
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();
switch (accessLevel) {
case EAccessLevel.User:
// Users can't update albums
return Forbid();
case EAccessLevel.Curator:
// Curators can only update albums they own
if (album.UserOwnerId != uid) return Forbid();
break;
case EAccessLevel.Admin:
// Admins can update any album
break;
}
// Update album properties
album.Title = albumDto.Name ?? album.Title;
album.UpdatedAt = DateTime.UtcNow;
album.PersonOwnerId = albumDto.Person ?? album.PersonOwnerId;
album.UserOwnerId = albumDto.Owner ?? album.UserOwnerId;
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();
}
///
/// Updates multiple albums in a bulk operation.
///
/// The bulk update data with album IDs and shared values.
/// 200 on success, 403 if forbidden.
[HttpPost]
[Authorize(Roles = "Admin, Curator")]
public ActionResult BulkUpdate([FromBody] BulkDto 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.Curator:
// Curators can only update albums they own
albums = albums.Where(x => x.UserOwnerId == uid).ToList();
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.UserOwnerId = bulkDto.Data.Owner ?? album.UserOwnerId;
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();
}
///
/// Creates a new album.
///
/// The album creation data.
/// 200 on success.
[HttpPut]
[Authorize(Roles = "Admin, Curator")]
public ActionResult Create([FromBody] AlbumCreateDto albumDto) {
var uid = authService.GetUserData(User)?.Id;
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
// If not admin, the owner of a new album is the current user
albumDto.Owner = accessLevel == EAccessLevel.Admin ? albumDto.Owner : uid;
var assets = albumDto.Assets != null ? assetRepository.FindBulk(albumDto.Assets).ToList() : new List();
var album = new Album {
Id = Guid.NewGuid(),
Title = albumDto.Name,
CreatedAt = DateTime.UtcNow,
PersonOwnerId = albumDto.Person ?? null,
UserOwnerId = albumDto.Owner,
CoverAssetId = albumDto.CoverAssetId ?? assets.OrderBy(a => a.OriginalFilename).FirstOrDefault()?.Id,
Assets = assets
};
albumRepository.Insert(album);
albumRepository.Save();
return Ok();
}
///
/// Removes multiple albums (soft delete).
///
/// The list of album IDs to remove.
/// 200 on success, 403 if forbidden.
[HttpDelete]
[Authorize(Roles = "Admin, Curator")]
public ActionResult BulkDelete([FromBody] List albumIds) {
var uid = authService.GetUserData(User)?.Id;
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
var albums = albumRepository.FindBulk(albumIds).ToList();
// If the user is not an admin, filter out albums they do not own
if (accessLevel != EAccessLevel.Admin) { albums = albums.Where(x => x.UserOwnerId == uid).ToList(); }
// If no albums are left after filtering, return a 403
if (albumIds.Count == 0) return Forbid();
albums.ForEach(albumRepository.Remove);
albumRepository.Save();
return Ok();
}
///
/// Removes an album by its ID (soft delete).
///
/// The album ID.
/// 200 on success, 404 if not found, 403 if forbidden.
[HttpDelete("{id}")]
[Authorize(Roles = "Admin, Curator")]
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 the user is not the owner or an admin, return a 403
if (accessLevel != EAccessLevel.Admin && album.UserOwnerId != uid) return Forbid();
albumRepository.Remove(album);
albumRepository.Save();
return Ok();
}
}