From 12eaa5814ef20b0910e8d64a753378b6f6797989 Mon Sep 17 00:00:00 2001 From: Jake Mannens Date: Fri, 22 May 2026 00:52:16 +1000 Subject: Initial commit --- Server/Controllers/ApiMediaController.cs | 219 +++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 Server/Controllers/ApiMediaController.cs (limited to 'Server/Controllers/ApiMediaController.cs') diff --git a/Server/Controllers/ApiMediaController.cs b/Server/Controllers/ApiMediaController.cs new file mode 100644 index 0000000..5a8ef21 --- /dev/null +++ b/Server/Controllers/ApiMediaController.cs @@ -0,0 +1,219 @@ +using HyperBooru.ApiModels; +using HyperBooru.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; + +namespace HyperBooru.Controllers; + +[ApiController] +[Route("/api/media")] +public class ApiMediaController : Controller { + private IDbContextFactory dbFactory; + private IMediaService mediaService; + + public ApiMediaController(IDbContextFactory dbFactory, IMediaService mediaService) { + this.dbFactory = dbFactory; + this.mediaService = mediaService; + } + + [HttpGet("{mediaId}")] + public async Task Get([FromRoute] Guid mediaId) { + using var db = dbFactory.CreateDbContext(); + + var media = await db.Media.FirstOrDefaultAsync(m => m.Guid == mediaId); + + if(media is null) + throw new ObjectNotFoundException(mediaId); + + return Ok((ApiModels.Media) media); + } + + [HttpGet("{mediaId}/files")] + public async Task GetUploadedFiles([FromRoute] Guid mediaId) { + using var db = dbFactory.CreateDbContext(); + + var media = await db.Media + .Include(m => m.UploadedFiles) + .FirstOrDefaultAsync(m => m.Guid == mediaId); + + if(media is null) + throw new ObjectNotFoundException(mediaId); + + return Ok(media.UploadedFiles.Select(uf => (ApiModels.UploadedFile) uf).ToArray()); + } + + [HttpPatch] + public async Task UpdateMedia([FromBody] ApiModels.Media updatedMedia) { + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var media = await db.Media.FirstOrDefaultAsync(m => m.Guid == updatedMedia.MediaId); + if(media is null) + return NotFound(); + + media.ShortDescription = updatedMedia.ShortDescription; + media.LongDescription = updatedMedia.LongDescription; + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(); + } + + [HttpPost] + public IActionResult Upload() { + if(Request.Form.Files.Count == 0) + return BadRequest("No files"); + if(Request.Form.Files.Count > 1) + return BadRequest("More than one file supplied"); + + var metadataString = Request.Form.Files + .First() + .Headers["X-HyperBooru-Metadata"] + .ElementAtOrDefault(0); + + MediaUploadRequest? metadata = metadataString is null ? null : + JsonSerializer.Deserialize(metadataString); + + var formFile = Request.Form.Files.First(); + + var media = mediaService.Create( + formFile.OpenReadStream(), + formFile.FileName, + metadata?.Checksum, + metadata?.LastAccessTime, + metadata?.LastWriteTime, + metadata?.CreateTime, + metadata?.Path, + metadata?.PathType, + metadata?.Tags); + + return Ok((ApiModels.Media) media); + } + + [HttpDelete("{mediaId}")] + public void Delete([FromRoute] Guid mediaId) => + mediaService.Delete(mediaId); + + [HttpGet("{mediaId}/tags")] + public async Task GetMediaTagsAsync([FromRoute] Guid mediaId) { + using var db = dbFactory.CreateDbContext(); + + var media = await db.Media + .Include(m => m.Tags) + .ThenInclude(t => t.TagDefinition) + .ThenInclude(td => td.ImplicitTags) + .FirstOrDefaultAsync(m => m.Guid == mediaId); + if(media is null) + return NotFound(); + + return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); + } + + [HttpPatch("{mediaId}/tags")] + public async Task AddTagsToMediaAsync( + [FromRoute] Guid mediaId, + [FromBody] Guid[] tagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var media = await db.Media + .Include(m => m.Tags) + .ThenInclude(t => t.TagDefinition) + .ThenInclude(td => td.ImplicitTags) + .FirstOrDefaultAsync(m => m.Guid == mediaId); + if(media is null) + return NotFound(); + + tagIds = tagIds.Distinct().ToArray(); + + var tags = await db.TagDefinitions + .Where(td => tagIds.Contains(td.Guid)) + .ToArrayAsync(); + + if(tags.Count() < tagIds.Count()) + return NotFound("Invalid tag IDs specified"); + + media.Tags.AddRange(tags + .Where(td => !media.Tags.Select(t => t.TagDefinition.Guid).Contains(td.Guid)) + .Select(td => new Tag() { TagDefinition = td })); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); + } + + [HttpPut("{mediaId}/tags")] + public async Task ReplaceMediaTagsAsync( + [FromRoute] Guid mediaId, + [FromBody] Guid[] tagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var media = await db.Media + .Include(m => m.Tags) + .ThenInclude(t => t.TagDefinition) + .ThenInclude(td => td.ImplicitTags) + .FirstOrDefaultAsync(m => m.Guid == mediaId); + if(media is null) + return NotFound(); + + tagIds = tagIds.Distinct().Order().ToArray(); + var tags = await db.TagDefinitions + .Where(td => tagIds.Contains(td.Guid)) + .ToArrayAsync(); + + var missingTags = tagIds.Except(tags.Select(td => td.Guid)); + var missingTagsString = string.Join(", ", missingTags.Select(t => t.ToString())); + if(missingTags.Any()) + return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); + + media.Tags.AddRange(tags + .Where(td => !media.Tags.Select(t => t.TagDefinition.Guid).Contains(td.Guid)) + .Select(td => new Tag() { TagDefinition = td })); + + db.Tags.RemoveRange( + media.Tags.Where(t => !tagIds.Contains(t.TagDefinition.Guid))); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); + } + + [HttpPatch("{mediaId}/tags/delete")] + public async Task DeleteTagsFromMediaAsync( + [FromRoute] Guid mediaId, + [FromBody] Guid[] tagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var media = await db.Media + .Include(m => m.Tags) + .ThenInclude(t => t.TagDefinition) + .ThenInclude(td => td.ImplicitTags) + .FirstOrDefaultAsync(m => m.Guid == mediaId); + if(media is null) + return NotFound(); + + tagIds = tagIds.Distinct().Order().ToArray(); + + var missingTags = tagIds.Except(media.Tags.Select(t => t.TagDefinition.Guid)); + var missingTagsString = string.Join(", ", missingTags.Select(t => t.ToString())); + if(missingTags.Any()) + return BadRequest($"Media does not contain the following tags: {missingTagsString}"); + + db.Tags.RemoveRange( + media.Tags.Where(t => tagIds.Contains(t.TagDefinition.Guid))); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); + } +} -- cgit v1.3