diff options
| author | Jake Mannens <jake@asger.xyz> | 2026-05-22 00:52:16 +1000 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2026-05-23 22:22:55 +1000 |
| commit | 12eaa5814ef20b0910e8d64a753378b6f6797989 (patch) | |
| tree | 062cf477c29054e0f089cb80f0cd79a9f3b7ccd9 /Server/Controllers | |
| parent | 6de5d7f5364fe1d54703da6d6b7cb08ea26e939f (diff) | |
Initial commitwasm-initial
Diffstat (limited to 'Server/Controllers')
| -rw-r--r-- | Server/Controllers/ApiFeedController.cs | 23 | ||||
| -rw-r--r-- | Server/Controllers/ApiMediaController.cs | 219 | ||||
| -rw-r--r-- | Server/Controllers/ApiTagController.cs | 252 | ||||
| -rw-r--r-- | Server/Controllers/ApiUserController.cs | 109 | ||||
| -rw-r--r-- | Server/Controllers/MediaController.cs | 154 |
5 files changed, 757 insertions, 0 deletions
diff --git a/Server/Controllers/ApiFeedController.cs b/Server/Controllers/ApiFeedController.cs new file mode 100644 index 0000000..fb260e6 --- /dev/null +++ b/Server/Controllers/ApiFeedController.cs @@ -0,0 +1,23 @@ +using HyperBooru.ApiModels; +using HyperBooru.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace HyperBooru.Controllers; + +[ApiController] +[Route("/api/feed")] +public class ApiFeedController : Controller { + private IFeedService feedService; + + public ApiFeedController(IDbContextFactory<HBContext> dbFactory, IFeedService feedService) => + this.feedService = feedService; + + [HttpPost] + public IActionResult FetchChunkAsync([FromBody] FeedRequest feedRequest) { + if(feedRequest.Count > 1000) + return BadRequest("Total number of requested items exceeds maximum"); + + return Ok(feedService.LoadChunk(feedRequest).Select(m => m.Guid).ToArray()); + } +} 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<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) + 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<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, + 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) + return NotFound(); + + 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 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<IActionResult> 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<IActionResult> 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()); + } +} diff --git a/Server/Controllers/ApiTagController.cs b/Server/Controllers/ApiTagController.cs new file mode 100644 index 0000000..e8417d2 --- /dev/null +++ b/Server/Controllers/ApiTagController.cs @@ -0,0 +1,252 @@ +using HyperBooru.ApiModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace HyperBooru.Controllers; + +[ApiController] +[Route("/api/tag")] +public class ApiTagController : Controller { + private IDbContextFactory<HBContext> dbFactory; + + public ApiTagController(IDbContextFactory<HBContext> dbFactory) => + this.dbFactory = dbFactory; + + [HttpGet("definition")] + public async Task<IActionResult> GetAllTagDefinitionsAsync() { + using var db = dbFactory.CreateDbContext(); + + var definitions = await db.TagDefinitions + .Include(td => td.ImplicitTags) + .Select(td => (ApiModels.TagDefinition)td) + .ToArrayAsync(); + + return Ok(definitions); + } + + [HttpGet("definition/{tagDefinitionId}")] + public async Task<IActionResult> GetTagDefinitionAsync([FromRoute] Guid tagDefinitionId) { + using var db = dbFactory.CreateDbContext(); + + var tagDefinition = await db.TagDefinitions + .Include(td => td.ImplicitTags) + .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); + + return tagDefinition is not null ? Ok(tagDefinition) : NotFound(); + } + + [HttpPost("definition")] + public async Task<IActionResult> CreateTagDefinitionAsync([FromBody] TagCreateRequest request) { + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + if(db.TagDefinitions.Any(td => td.Name == request.Name)) + return BadRequest("Name already exists"); + + if(request.Alias is not null) + if(db.TagDefinitions.Any(td => td.Alias == request.Alias)) + return BadRequest("Alias already exists"); + + List<TagDefinition> implicitTags = new(); + if(request.ImplicitTags is not null) { + implicitTags = await db.TagDefinitions + .Where(td => request.ImplicitTags.Distinct().Contains(td.Guid)) + .ToListAsync(); + } + + var tagDefinition = new TagDefinition { + Source = TagSource.UserTag, + Namespace = request.Namespace, + Name = request.Name, + Alias = request.Alias, + ImplicitTags = implicitTags + }; + + db.TagDefinitions.Add(tagDefinition); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok((ApiModels.TagDefinition) tagDefinition); + } + + [HttpDelete("definition/{tagDefinitionId}")] + public async Task<IActionResult> DeleteTagDefinitionAsync([FromRoute] Guid tagDefinitionId) { + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var tagDefinition = await db.TagDefinitions + .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); + + if(tagDefinition is null) + return NotFound("Tag definition not found"); + + if(tagDefinition.ObjectId < 0) + return BadRequest("Cannot delete built-in tag definition"); + + db.TagDefinitions.Remove(tagDefinition); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(); + } + + [HttpPatch("definition/{tagDefinitionId}")] + public async Task<IActionResult> UpdateTagDefinitionAsync( + [FromRoute] Guid tagDefinitionId, + [FromBody] TagUpdateRequest request) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var tagDefinition = await db.TagDefinitions + .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); + + if(tagDefinition is null) + return NotFound("Tag definition not found"); + + if(tagDefinition.ObjectId < 0) + return BadRequest("Cannot update built-in tag definition"); + + if(request.Name is not null) + if(db.TagDefinitions.Any(td => td.Name == request.Name)) + return BadRequest("Name already exists"); + + if(request.Alias is not null) + if(db.TagDefinitions.Any(td => td.Alias == request.Alias)) + return BadRequest("Alias already exists"); + + tagDefinition.Namespace = request.Namespace ?? tagDefinition.Namespace; + tagDefinition.Name = request.Name ?? tagDefinition.Name; + tagDefinition.Alias = request.Alias ?? tagDefinition.Alias; + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok((ApiModels.TagDefinition) tagDefinition); + } + + [HttpPatch("definition/{tagDefinitionId}/implicit")] + public async Task<IActionResult> AddImplicitTagsAsync( + [FromRoute] Guid tagDefinitionId, + [FromBody] Guid[] implicitTagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var tagDefinition = await db.TagDefinitions + .Include(td => td.ImplicitTags) + .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); + + if(tagDefinition is null) + return NotFound("Tag definition not found"); + + if(tagDefinition.ObjectId < 0) + return BadRequest("Cannot update built-in tag definition"); + + implicitTagIds = implicitTagIds.Distinct().ToArray(); + + var implicitTags = db.TagDefinitions + .Where(td => implicitTagIds.Contains(td.Guid)) + .ToArray(); + + var missingTags = implicitTagIds.Except(implicitTags.Select(td => td.Guid)); + var missingTagsString = string.Join(", ", missingTags.Select(td => td.ToString())); + if(missingTags.Any()) + return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); + + tagDefinition.ImplicitTags.AddRange( + implicitTags.ExceptBy(tagDefinition.ImplicitTags.Select(td => td.Guid), td => td.Guid)); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(); + } + + [HttpPut("definition/{tagDefinitionId}/implicit")] + public async Task<IActionResult> ReplaceImplicitTagsAsync( + [FromRoute] Guid tagDefinitionId, + [FromBody] Guid[] implicitTagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var tagDefinition = await db.TagDefinitions + .Include(td => td.ImplicitTags) + .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); + + if(tagDefinition is null) + return NotFound("Tag definition not found"); + + if(tagDefinition.ObjectId < 0) + return BadRequest("Cannot update built-in tag definition"); + + implicitTagIds = implicitTagIds.Distinct().ToArray(); + + var implicitTags = db.TagDefinitions + .Where(td => implicitTagIds.Contains(td.Guid)) + .ToArray(); + + var missingTags = implicitTagIds.Except(implicitTags.Select(td => td.Guid)); + var missingTagsString = string.Join(", ", missingTags.Select(td => td.ToString())); + if(missingTags.Any()) + return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); + + tagDefinition.ImplicitTags.AddRange( + implicitTags.ExceptBy(tagDefinition.ImplicitTags.Select(td => td.Guid), td => td.Guid)); + + var toRemove = tagDefinition.ImplicitTags + .Where(td => !implicitTags.Select(td => td.Guid).Contains(td.Guid)) + .ToArray(); + + foreach(var td in toRemove) + tagDefinition.ImplicitTags.Remove(td); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(); + } + + [HttpPost("definition/{tagDefinitionId}/implicit/delete")] + public async Task<IActionResult> DeleteImplicitTagAsync( + [FromRoute] Guid tagDefinitionId, + [FromBody] Guid[] implicitTagIds) { + + using var db = dbFactory.CreateDbContext(); + using var transaction = await db.Database.BeginTransactionAsync(); + + var tagDefinition = await db.TagDefinitions + .Include(td => td.ImplicitTags) + .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); + + if(tagDefinition is null) + return NotFound("Tag definition not found"); + + if(tagDefinition.ObjectId < 0) + return BadRequest("Cannot update built-in tag definition"); + + implicitTagIds = implicitTagIds.Distinct().ToArray(); + + var missingTagIds = implicitTagIds + .Except(tagDefinition.ImplicitTags.Select(td => td.Guid)); + var missingTagsString = string.Join(", ", missingTagIds.Select(td => td.ToString())); + if(missingTagIds.Any()) + return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); + + var toRemove = tagDefinition.ImplicitTags + .Where(td => !implicitTagIds.Contains(td.Guid)) + .ToArray(); + + foreach(var td in toRemove) + tagDefinition.ImplicitTags.Remove(td); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok(); + } +} diff --git a/Server/Controllers/ApiUserController.cs b/Server/Controllers/ApiUserController.cs new file mode 100644 index 0000000..d678287 --- /dev/null +++ b/Server/Controllers/ApiUserController.cs @@ -0,0 +1,109 @@ +using HyperBooru.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace HyperBooru.Controllers; + +[ApiController] +[Authorize] +[Route("/api/user")] +public class ApiUserController : Controller { + private IDbContextFactory<HBContext> dbFactory; + + public ApiUserController(IDbContextFactory<HBContext> dbFactory) => + this.dbFactory = dbFactory; + + [HttpGet] + public async Task<IActionResult> GetAllUsersAsync() { + using var db = dbFactory.CreateDbContext(); + + return Ok(await db.Users + .Select(u => (ApiModels.User) u) + .ToArrayAsync()); + } + + [HttpGet("{userId}")] + public async Task<IActionResult> GetUserAsync([FromRoute] Guid userId) { + using var db = dbFactory.CreateDbContext(); + + var user = await db.Users + .FirstOrDefaultAsync(u => u.Guid == userId); + + return user is null ? NotFound() : Ok((ApiModels.User) user); + } + + [HttpPost] + public async Task<IActionResult> CreateUserAsync([FromBody] ApiModels.UserCreateRequest request) { + using var db = dbFactory.CreateDbContext(); + + using var transaction = await db.Database.BeginTransactionAsync(); + + if(await db.Users.AnyAsync(u => u.Username == request.Username)) + return BadRequest("Username already exists"); + + var user = new User() { + Username = request.Username, + PasswordHash = UserService.HashPassword(request.Password) + }; + + db.Users.Add(user); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok((ApiModels.User) user); + } + + [HttpPatch("{userId}")] + public async Task<IActionResult> UpdateUserAsync( + [FromRoute] Guid userId, + [FromBody] ApiModels.UserUpdateRequest request) { + + using var db = dbFactory.CreateDbContext(); + + using var transaction = await db.Database.BeginTransactionAsync(); + + var user = await db.Users.FirstOrDefaultAsync(u => u.Guid == userId); + if(user is null) + return NotFound(); + + if(request.Username is not null) { + if(string.IsNullOrWhiteSpace(request.Username)) + return BadRequest("Username cannot be empty"); + user.Username = request.Username; + } + + if(request.Password is not null) { + if(string.IsNullOrWhiteSpace(request.Password)) + return BadRequest("Password cannot be empty"); + user.PasswordHash = UserService.HashPassword(request.Password); + } + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok((ApiModels.User) user); + } + + [HttpDelete("{userId}")] + public async Task<IActionResult> DeleteUserAsync([FromRoute] Guid userId) { + if(userId == HBContext.AdminUser) + return BadRequest("Cannot delete the admin user"); + + using var db = dbFactory.CreateDbContext(); + + using var transaction = await db.Database.BeginTransactionAsync(); + + var user = await db.Users.FirstOrDefaultAsync(u => u.Guid == userId); + if(user is null) + return NotFound(); + + db.Users.Remove(user); + + await db.SaveChangesAsync(); + await transaction.CommitAsync(); + + return Ok((ApiModels.User) user); + } +} diff --git a/Server/Controllers/MediaController.cs b/Server/Controllers/MediaController.cs new file mode 100644 index 0000000..27c3cbd --- /dev/null +++ b/Server/Controllers/MediaController.cs @@ -0,0 +1,154 @@ +using HyperBooru.ApiModels; +using HyperBooru.Services; +using HyperBooru.Util; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace HyperBooru.Controllers; + +[ApiController] +[Route("/media")] +public class MediaController : Controller { + private IHttpContextAccessor httpContextAccessor; + private IMediaService mediaService; + private IConfigService config; + private HBContext db; + + private readonly string[] FormatPriority = [ + "image/webp", + "image/png" + ]; + + public MediaController( + IHttpContextAccessor httpContextAccessor, + IMediaService mediaService, + IConfigService config, + HBContext db) { + + this.httpContextAccessor = httpContextAccessor; + this.mediaService = mediaService; + this.config = config; + this.db = db; + } + + [HttpGet("{mediaId}")] + public IActionResult Fetch([FromRoute] Guid mediaId) { + var media = db.Media + .Include(m => m.CurrentUploadedFile) + .First(m => m.Guid == mediaId); + if(media is null) + return NotFound(); + + // Check if the requested media item is a HEIC image and if it is, convert it + // otherwise, return the original file content, unaltered + if(media.CurrentUploadedFile!.MimeType == "image/heic") { + // If the media needs to be converted, check the HTTP request for allowed + // media formats, and convert to the best available format or WebP otherwise + var allowedTypes = httpContextAccessor + .HttpContext? + .Request + .GetTypedHeaders().Accept.Select(h => h.MediaType.ToString()) ?? Array.Empty<string>(); + + var format = FormatPriority.FirstOrDefault(f => allowedTypes.Contains(f)) ?? "image/webp"; + + var fs = mediaService.GetConverted(media, format); + + return new FileStreamResult(fs, format); + } else { + var fs = System.IO.File.OpenRead(mediaService.GetPath(media)); + return new FileStreamResult(fs, media.CurrentUploadedFile!.MimeType); + } + } + + [HttpGet("thumb/{mediaId}")] + public IActionResult Thumbnail( + [FromRoute] Guid mediaId, + [FromQuery(Name = "w")] int? width, + [FromQuery(Name = "h")] int? height) { + + try { + var thumb = mediaService.GetThumbnail(mediaId, width, height); + return new FileStreamResult(thumb, "image/jpeg"); + } catch(ThumbnailException e) { + return BadRequest(e.Message); + } catch(ObjectNotFoundException e) { + return NotFound(e.Message); + } + } + + [HttpDelete("{mediaId}")] + public void Delete([FromRoute] Guid mediaId) { + mediaService.Delete(mediaId); + } + + [HttpPost] + public IActionResult Upload() { + if(Request.Form.Files.Count == 0) + return BadRequest("No files"); + + Media media = new(); + + foreach(var formFile in Request.Form.Files) { + try { + // Parse timestamps from headers + DateTime? lastAccessTime = + formFile.Headers["X-HyperBooru-LastAccessTime"] + .ElementAtOrDefault(0)? + .TryParseDateTimeUtc(); + DateTime? lastWriteTime = + formFile.Headers["X-HyperBooru-LastWriteTime"] + .ElementAtOrDefault(0)? + .TryParseDateTimeUtc(); + DateTime? createTime = + formFile.Headers["X-HyperBooru-CreateTime"] + .ElementAtOrDefault(0)? + .TryParseDateTimeUtc(); + + // Parse original path from headers + string? path = + formFile.Headers["X-HyperBooru-Path"] + .ElementAtOrDefault(0); + + object? pathType = null; + string? pathTypeString = + formFile.Headers["X-HyperBooru-PathType"] + .ElementAtOrDefault(0); + Enum.TryParse(typeof(PathType), pathTypeString, true, out pathType); + + // Parse tag IDs from headers + Guid[]? tagIds = formFile.Headers["X-HyperBooru-Tags"] + .ElementAtOrDefault(0)? + .Split(',') + .Select(t => Guid.Parse(t)) + .ToArray(); + + media = mediaService.Create( + formFile.OpenReadStream(), + formFile.FileName, + formFile.Headers["X-HyperBooru-Checksum"] + .ElementAtOrDefault(0), + lastAccessTime, + lastWriteTime, + createTime, + path, + (PathType?) pathType, + tagIds); + + // Return the GUID of the new media object if requested + bool returnMetadataParsed = bool.TryParse( + formFile.Headers["X-HyperBooru-ReturnMediaId"], out var returnMetadata); + + if(returnMetadataParsed && returnMetadata) + return Content(media.Guid.ToString()); + } catch(MediaCreateException e) { + return BadRequest(e.Message); + } + } + + if(Request.Form.Files.Count == 1) + return Redirect($"/ViewMedia?m={media.Guid}"); + else + return Redirect($"/Gallery"); + } +}
\ No newline at end of file |
