diff options
| author | Jake Mannens <jake@asger.xyz> | 2026-03-17 03:04:36 +1100 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2026-06-07 12:32:37 +1000 |
| commit | c51ff4e755f009ca0bc8e935a92c04e583c4ee8a (patch) | |
| tree | 0a9a311c5404a96495df1047e613dc3aea3d0f15 /Controllers/ApiMediaController.cs | |
Initial commit
Diffstat (limited to 'Controllers/ApiMediaController.cs')
| -rw-r--r-- | Controllers/ApiMediaController.cs | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/Controllers/ApiMediaController.cs b/Controllers/ApiMediaController.cs new file mode 100644 index 0000000..bb6c81e --- /dev/null +++ b/Controllers/ApiMediaController.cs @@ -0,0 +1,227 @@ +using HyperBooru.ApiModels; +using HyperBooru.Services; +using HyperBooru.Util; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; + +namespace HyperBooru.Controllers; + +[ApiController] +[Authorize] +[Route("/api/media")] +public class ApiMediaController : Controller { + private IDbContextFactory<HBContext> dbFactory; + private IMediaService mediaService; + + public ApiMediaController(IDbContextFactory<HBContext> dbFactory, IMediaService mediaService) { + this.dbFactory = dbFactory; + this.mediaService = mediaService; + } + + [HttpGet("{mediaId}")] + public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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) + throw new ObjectNotFoundException([ updatedMedia.MediaId ]); + + media.ShortDescription = updatedMedia.ShortDescription?.NullIfEmpty(); + media.LongDescription = updatedMedia.LongDescription?.NullIfEmpty(); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(); + } + + [HttpPost] + public IActionResult Upload() { + if(Request.Form.Files.Count == 0) + throw new ApiModels.ArgumentException("No files"); + if(Request.Form.Files.Count > 1) + throw new ApiModels.ArgumentException("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<MediaUploadRequest>(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.NullIfEmpty(), + 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<IActionResult> 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) + throw new ObjectNotFoundException([ mediaId ]); + + return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); + } + + [HttpPatch("{mediaId}/tags")] + public async Task<IActionResult> AddTagsToMediaAsync( + [FromRoute] Guid mediaId, + [FromBody] Guid[] tagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var missing = new List<Guid>(); + + 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) + missing.Add(mediaId); + + tagIds = tagIds.Distinct().ToArray(); + + var tags = await db.TagDefinitions + .Where(td => tagIds.Contains(td.Guid)) + .ToArrayAsync(); + + missing.AddRange(tagIds.Except(tags.Select(td => td.Guid))); + if(missing.Any()) + throw new ObjectNotFoundException(missing); + + 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<IActionResult> ReplaceMediaTagsAsync( + [FromRoute] Guid mediaId, + [FromBody] Guid[] tagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var missing = new List<Guid>(); + + 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) + missing.Add(mediaId); + + tagIds = tagIds.Distinct().Order().ToArray(); + var tags = await db.TagDefinitions + .Where(td => tagIds.Contains(td.Guid)) + .ToArrayAsync(); + + missing.AddRange(tagIds.Except(tags.Select(td => td.Guid))); + if(missing.Any()) + throw new ObjectNotFoundException(missing); + + 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<IActionResult> DeleteTagsFromMediaAsync( + [FromRoute] Guid mediaId, + [FromBody] Guid[] tagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var missing = new List<Guid>(); + + 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) + missing.Add(mediaId); + + tagIds = tagIds.Distinct().Order().ToArray(); + + missing.AddRange(tagIds.Except(media.Tags.Select(t => t.TagDefinition.Guid))); + if(missing.Any()) + throw new ObjectNotFoundException(missing); + + 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()); + } +} |
