Files
MilkyShots/Lactose/Controllers/AssetController.cs

306 lines
11 KiB
C#

using Butter.Dtos;
using Butter.Dtos.Asset;
using Butter.Types;
using Lactose.Mapper;
using Lactose.Models;
using Lactose.Repositories;
using Lactose.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace Lactose.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AssetController(
ILogger<AssetController> logger,
LactoseAuthService authService,
IAssetRepository assetRepository,
IUserRepository userRepository
) : ControllerBase {
[HttpGet("{id}")]
public ActionResult<AssetDto> Get(Guid id) {
var uid = authService.GetUserData(User)?.Id;
EAccessLevel accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
logger.LogTrace(
$"""
Requested asset: {id}
by user: {uid}
access level: {accessLevel}
"""
);
var asset = assetRepository.Find(id);
if (asset == null) {
logger.LogWarning($"Request asset {id} not found in the database!");
return NotFound();
}
switch (accessLevel) {
case EAccessLevel.User: {
//Not publicly shared && Not shared with the user
if (!asset.IsPubliclyShared && asset.SharedWith?.Find(x => x.Id == uid) == null) {
logger.LogTrace($"{asset.Id} is not shared with user {uid}");
return Unauthorized();
}
//Deleted asset
if (asset.DeletedAt != null) {
logger.LogTrace($"{asset.Id} is deleted and thus not exists for this user");
return NotFound();
}
break;
}
case EAccessLevel.Curator: {
//Deleted asset owned by the user
if (asset.DeletedAt != null)
if (asset.Owner?.Id != uid) {
logger.LogTrace(
$"{asset.Id} is deleted and it's not owned by this user so it does not exists for this user"
);
return NotFound();
} else { logger.LogTrace($"{asset.Id} is deleted but it's owned by this user so it exists for this user"); }
break;
}
case EAccessLevel.Admin: {
//Admins will see this regardless
logger.LogTrace($"{asset.Id} is being requested by an admin, sending it regardless of the visibility state");
break;
}
}
AssetDto dto = asset.ToFullAssetsDto(accessLevel);
logger.LogTrace(
$"""
Outbound DTO:
dto.Id: {dto.Id}
dto.FileName: {dto.FileName}
dto.AssetType: {dto.AssetType}
dto.IsPublic: {dto.IsPublic}
dto.CreatedAt: {dto.CreatedAt}
dto.UpdatedAt: {dto.UpdatedAt}
dto.DeletedAt: {dto.DeletedAt}
dto.Owner: {dto.Owner.Id} {dto.Owner.Username}
"""
);
return Ok(dto);
}
[HttpGet]
public ActionResult<List<AssetPreviewDto>> GetAll([FromQuery] AssetSearchOptionsDto searchOptionsDto) {
var uid = authService.GetUserData(User)?.Id;
EAccessLevel accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
DateTime from = DateTime.Parse(searchOptionsDto.StartDate ?? string.Empty);
DateTime to = DateTime.Parse(searchOptionsDto.EndDate ?? string.Empty);
if (from > to) {
logger.LogWarning("Invalid date range provided");
return BadRequest();
}
if (searchOptionsDto.Page < 1 || searchOptionsDto.PageSize < 1) {
logger.LogWarning("Invalid pagination parameters provided");
return BadRequest();
}
var assets = assetRepository.FindByDateRange(from, to, searchOptionsDto.Page, searchOptionsDto.PageSize);
logger.LogTrace(
$"""
Requested assets
by user: {uid}
access level: {accessLevel}
date range: {from} - {to}
"""
);
switch (accessLevel) {
case EAccessLevel.User: {
//Only publicly shared assets and assets shared with the user that are not deleted
assets = assets.Where(
a => a.IsPubliclyShared || a.SharedWith?.Find(x => x.Id == uid) != null && a.DeletedAt != null
);
break;
}
case EAccessLevel.Curator:
case EAccessLevel.Admin: {
//Admins will see this regardless
break;
}
}
return Ok(assets.ToAssetPreviewDto());
}
[HttpPost("{id}")]
[Authorize(Roles = "Curator,Admin")]
public async Task<IStatusCodeActionResult> Update([FromRoute] Guid id, [FromBody] AssetUpdateDto dto) {
var uid = authService.GetUserData(User)?.Id;
var accesslevel = authService.GetUserData(User)!.AccessLevel;
var asset = assetRepository.Find(id);
if (asset == null) {
logger.LogWarning($"Request asset {id} not found in the database!");
return NotFound();
}
if (asset.Owner?.Id != uid && accesslevel != EAccessLevel.Admin) {
logger.LogWarning($"User {authService.GetUserData(User)?.Id} is trying to update asset {id} that they do not own");
return Unauthorized();
}
string log = "";
asset.IsPubliclyShared = dto.IsPublic ?? asset.IsPubliclyShared;
log += $"PubliclyShared: {asset.IsPubliclyShared} -> {dto.IsPublic ?? asset.IsPubliclyShared}\n";
asset.DeletedAt = dto.DeletedAt ?? asset.DeletedAt;
log += $"DeletedAt: {asset.DeletedAt} -> {dto.DeletedAt ?? asset.DeletedAt}\n";
asset.Owner = dto.Owner != null ? await userRepository.FindAsync(dto.Owner.Value) : asset.Owner;
log += $"Owner: {asset.Owner?.Id} -> {dto.Owner ?? asset.Owner?.Id}\n";
logger.LogTrace($"Updating asset {id}\n{log}");
assetRepository.Update(asset);
assetRepository.Save();
return Ok();
}
[HttpPost]
[Authorize(Roles = "Curator,Admin")]
public async Task<IStatusCodeActionResult> BulkUpdate([FromBody] BulkDto<AssetUpdateDto> bulkDto) {
var uid = authService.GetUserData(User)?.Id;
var accesslevel = authService.GetUserData(User)!.AccessLevel;
var assets = assetRepository.FindBulk(bulkDto.Ids);
var enumerable = assets as List<Asset> ?? assets.ToList();
if (enumerable.Count() != bulkDto.Ids.Count) {
logger.LogWarning(
$"Some assets were not found in the database" +
$"\nrequested assets: {bulkDto.Ids.Count}, found assets: {enumerable.Count()}"
);
var missingIds = bulkDto.Ids.Except(enumerable.Select(a => a.Id)).ToList();
logger.LogTrace($"Missing IDs: {string.Join(", ", missingIds)}");
}
//If the user is not an admin, they can only update their own assets
if (accesslevel != EAccessLevel.Admin) enumerable = enumerable.Where(x => x.OwnerId == uid).ToList();
foreach (var x in enumerable) {
x.IsPubliclyShared = bulkDto.Data.IsPublic ?? x.IsPubliclyShared;
x.DeletedAt = bulkDto.Data.DeletedAt ?? x.DeletedAt;
x.Owner = x.Owner;
if (bulkDto.Data.Owner != null) x.Owner = await userRepository.FindAsync(bulkDto.Data.Owner.Value);
}
assetRepository.UpdateBulk(enumerable);
return Ok();
}
[HttpDelete("{id}")]
[Authorize(Roles = "Curator,Admin")]
public IStatusCodeActionResult Delete([FromRoute] Guid id) {
var uid = authService.GetUserData(User)?.Id;
var accesslevel = authService.GetUserData(User)!.AccessLevel;
var asset = assetRepository.Find(id);
if (asset == null) {
logger.LogWarning($"Request asset {id} not found in the database!");
return NotFound();
}
if (asset.Owner?.Id != uid && accesslevel != EAccessLevel.Admin) {
logger.LogWarning($"User {authService.GetUserData(User)?.Id} is trying to delete asset {id} that they do not own");
return Unauthorized();
}
asset.DeletedAt = DateTime.Now;
assetRepository.Update(asset);
assetRepository.Save();
return Ok();
}
[HttpDelete]
[Authorize(Roles = "Curator,Admin")]
public IStatusCodeActionResult BulkDelete([FromBody] List<Guid> ids) {
var uid = authService.GetUserData(User)?.Id;
var accesslevel = authService.GetUserData(User)!.AccessLevel;
var assets = assetRepository.FindBulk(ids);
var enumerable = assets as List<Asset> ?? assets.ToList();
if (enumerable.Count() != ids.Count) {
logger.LogWarning(
$"Some assets were not found in the database" +
$"\nrequested assets: {ids.Count}, found assets: {enumerable.Count()}"
);
var missingIds = ids.Except(enumerable.Select(a => a.Id)).ToList();
logger.LogTrace($"Missing IDs: {string.Join(", ", missingIds)}");
}
//If the user is not an admin, they can only delete their own assets
if (accesslevel != EAccessLevel.Admin) enumerable = enumerable.Where(x => x.OwnerId == uid).ToList();
enumerable.ForEach(x => x.DeletedAt = DateTime.Now);
assetRepository.UpdateBulk(enumerable);
return Ok();
}
[NonAction]
[HttpPut]
[Authorize(Roles = "Curator,Admin")]
public ActionResult<Guid> Create([FromBody] AssetCreateDto dto) {
var uid = authService.GetUserData(User)?.Id;
var accesslevel = authService.GetUserData(User)!.AccessLevel;
if (userRepository.FindAsync(dto.Owner) == null) {
logger.LogWarning($"User {dto.Owner} does not exist in the database");
return BadRequest();
}
if (accesslevel != EAccessLevel.Admin && dto.Owner != uid) {
logger.LogWarning($"User {uid} is trying to create an asset for user {dto.Owner}");
return Unauthorized();
}
//TODO: This makes exactly zero sense. How are we supposed to create an asset externally here?
// Does this even need to exist?
var asset = new Asset {
Id = Guid.NewGuid(),
Type = dto.AssetType,
IsPubliclyShared = dto.IsPublic,
OwnerId = dto.Owner,
CreatedAt = dto.CreatedAt,
MimeType = dto.MimeType,
ResolutionWidth = dto.ResolutionWidth,
ResolutionHeight = dto.ResolutionHeight,
FileSize = dto.FileSize,
Duration = dto.Duration ?? 0,
FrameRate = dto.FrameRate ?? 0,
OriginalPath = String.Empty,
OriginalFilename = String.Empty
};
assetRepository.Insert(asset);
assetRepository.Save();
return Ok(asset.Id);
}
}