diff options
| -rw-r--r-- | Controllers/ApiFeedController.cs | 2 | ||||
| -rw-r--r-- | Controllers/ApiMediaController.cs | 50 | ||||
| -rw-r--r-- | Controllers/ApiStatisticsController.cs | 26 | ||||
| -rw-r--r-- | Controllers/ApiTagController.cs | 81 | ||||
| -rw-r--r-- | Controllers/ApiUserController.cs | 20 | ||||
| -rw-r--r-- | Controllers/MediaController.cs | 106 | ||||
| -rw-r--r-- | ExceptionMiddleware.cs | 6 | ||||
| -rw-r--r-- | HBContext.cs | 3 | ||||
| -rw-r--r-- | Pages/Component/MediaTagTable.razor | 2 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor | 2 | ||||
| -rw-r--r-- | Pages/TagDefinitions.razor | 6 | ||||
| -rw-r--r-- | Server.csproj | 16 | ||||
| -rw-r--r-- | Services/MediaService.cs | 4 | ||||
| -rw-r--r-- | Tag.cs | 9 | ||||
| -rw-r--r-- | Todo.md | 4 | ||||
| -rw-r--r-- | Util.cs | 5 |
16 files changed, 189 insertions, 153 deletions
diff --git a/Controllers/ApiFeedController.cs b/Controllers/ApiFeedController.cs index 382169e..068cc17 100644 --- a/Controllers/ApiFeedController.cs +++ b/Controllers/ApiFeedController.cs @@ -18,7 +18,7 @@ public class ApiFeedController : Controller { [HttpPost] public IActionResult FetchChunkAsync([FromBody] FeedRequest feedRequest) { if(feedRequest.Count > 1000) - return BadRequest("Total number of requested items exceeds maximum"); + throw new ApiModels.ArgumentException("Total number of requested items exceeds maximum"); return Ok(feedService.LoadChunk(feedRequest).Select(m => m.Guid).ToArray()); } diff --git a/Controllers/ApiMediaController.cs b/Controllers/ApiMediaController.cs index a1b07b1..bb6c81e 100644 --- a/Controllers/ApiMediaController.cs +++ b/Controllers/ApiMediaController.cs @@ -1,5 +1,6 @@ using HyperBooru.ApiModels; using HyperBooru.Services; +using HyperBooru.Util; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -26,7 +27,7 @@ public class ApiMediaController : Controller { var media = await db.Media.FirstOrDefaultAsync(m => m.Guid == mediaId); if(media is null) - throw new ObjectNotFoundException(mediaId); + throw new ObjectNotFoundException([ mediaId ]); return Ok((ApiModels.Media) media); } @@ -40,7 +41,7 @@ public class ApiMediaController : Controller { .FirstOrDefaultAsync(m => m.Guid == mediaId); if(media is null) - throw new ObjectNotFoundException(mediaId); + throw new ObjectNotFoundException([ mediaId ]); return Ok(media.UploadedFiles.Select(uf => (ApiModels.UploadedFile) uf).ToArray()); } @@ -52,10 +53,10 @@ public class ApiMediaController : Controller { var media = await db.Media.FirstOrDefaultAsync(m => m.Guid == updatedMedia.MediaId); if(media is null) - return NotFound(); + throw new ObjectNotFoundException([ updatedMedia.MediaId ]); - media.ShortDescription = updatedMedia.ShortDescription; - media.LongDescription = updatedMedia.LongDescription; + media.ShortDescription = updatedMedia.ShortDescription?.NullIfEmpty(); + media.LongDescription = updatedMedia.LongDescription?.NullIfEmpty(); await db.SaveChangesAsync(); await transaction.CommitAsync(); @@ -66,9 +67,9 @@ public class ApiMediaController : Controller { [HttpPost] public IActionResult Upload() { if(Request.Form.Files.Count == 0) - return BadRequest("No files"); + throw new ApiModels.ArgumentException("No files"); if(Request.Form.Files.Count > 1) - return BadRequest("More than one file supplied"); + throw new ApiModels.ArgumentException("More than one file supplied"); var metadataString = Request.Form.Files .First() @@ -87,7 +88,7 @@ public class ApiMediaController : Controller { metadata?.LastAccessTime, metadata?.LastWriteTime, metadata?.CreateTime, - metadata?.Path, + metadata?.Path.NullIfEmpty(), metadata?.PathType, metadata?.Tags); @@ -108,7 +109,7 @@ public class ApiMediaController : Controller { .ThenInclude(td => td.ImplicitTags) .FirstOrDefaultAsync(m => m.Guid == mediaId); if(media is null) - return NotFound(); + throw new ObjectNotFoundException([ mediaId ]); return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); } @@ -121,13 +122,15 @@ public class ApiMediaController : Controller { 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) - return NotFound(); + missing.Add(mediaId); tagIds = tagIds.Distinct().ToArray(); @@ -135,8 +138,9 @@ public class ApiMediaController : Controller { .Where(td => tagIds.Contains(td.Guid)) .ToArrayAsync(); - if(tags.Count() < tagIds.Count()) - return NotFound("Invalid tag IDs specified"); + 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)) @@ -156,23 +160,24 @@ public class ApiMediaController : Controller { 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) - return NotFound(); + missing.Add(mediaId); 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}"); + 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)) @@ -195,20 +200,21 @@ public class ApiMediaController : Controller { 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) - return NotFound(); + missing.Add(mediaId); 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}"); + 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))); diff --git a/Controllers/ApiStatisticsController.cs b/Controllers/ApiStatisticsController.cs new file mode 100644 index 0000000..3acd1d5 --- /dev/null +++ b/Controllers/ApiStatisticsController.cs @@ -0,0 +1,26 @@ +using HyperBooru.ApiModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace HyperBooru.Controllers; + +[ApiController] +[Route("/api/stats")] +public class ApiStatisticsController : Controller { + private IDbContextFactory<HBContext> dbFactory; + + public ApiStatisticsController(IDbContextFactory<HBContext> dbFactory) => + this.dbFactory = dbFactory; + + [HttpGet("ingest")] + public async Task<IActionResult> GetIngestStatistics() { + using var db = dbFactory.CreateDbContext(); + + return Ok(new IngestStatistics() { + TotalMediaCount = db.Media.Count(), + UntaggedMediaCount = db.Media + .Where(m => m.Tags.Any(t => t.TagDefinition.ObjectId == (int) HBObjectId.IngestTag)) + .Count(), + }); + } +} diff --git a/Controllers/ApiTagController.cs b/Controllers/ApiTagController.cs index f48cc05..d1e49ee 100644 --- a/Controllers/ApiTagController.cs +++ b/Controllers/ApiTagController.cs @@ -34,7 +34,10 @@ public class ApiTagController : Controller { .Include(td => td.ImplicitTags) .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); - return tagDefinition is not null ? Ok(tagDefinition) : NotFound(); + if(tagDefinition is null) + throw new ObjectNotFoundException([ tagDefinitionId ]); + + return Ok(tagDefinition); } [HttpPost("definition")] @@ -42,12 +45,12 @@ public class ApiTagController : Controller { 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"); + var nameExists = db.TagDefinitions.Any(td => td.Name == request.Name); + var aliasExists = + request.Alias is not null && db.TagDefinitions.Any(td => td.Alias == request.Alias); - if(request.Alias is not null) - if(db.TagDefinitions.Any(td => td.Alias == request.Alias)) - return BadRequest("Alias already exists"); + if(nameExists || aliasExists) + throw new TagDuplicateException(nameExists, aliasExists); List<TagDefinition> implicitTags = new(); if(request.ImplicitTags is not null) { @@ -81,10 +84,10 @@ public class ApiTagController : Controller { .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) - return NotFound("Tag definition not found"); + throw new ObjectNotFoundException([ tagDefinitionId ]); if(tagDefinition.ObjectId < 0) - return BadRequest("Cannot delete built-in tag definition"); + throw new ApiModels.ArgumentException("Cannot delete built-in tag definition"); db.TagDefinitions.Remove(tagDefinition); @@ -106,18 +109,18 @@ public class ApiTagController : Controller { .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) - return NotFound("Tag definition not found"); + throw new ObjectNotFoundException([ tagDefinitionId ]); if(tagDefinition.ObjectId < 0) - return BadRequest("Cannot update built-in tag definition"); + throw new ApiModels.ArgumentException("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"); + var nameExists = + request.Name is not null && db.TagDefinitions.Any(td => td.Name == request.Name); + var aliasExists = + request.Alias is not null && db.TagDefinitions.Any(td => td.Alias == request.Alias); - if(request.Alias is not null) - if(db.TagDefinitions.Any(td => td.Alias == request.Alias)) - return BadRequest("Alias already exists"); + if(nameExists || aliasExists) + throw new TagDuplicateException(nameExists, aliasExists); tagDefinition.Namespace = request.Namespace ?? tagDefinition.Namespace; tagDefinition.Name = request.Name ?? tagDefinition.Name; @@ -137,15 +140,14 @@ public class ApiTagController : Controller { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); + var missing = new List<Guid>(); + 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"); + missing.Add(tagDefinitionId); implicitTagIds = implicitTagIds.Distinct().ToArray(); @@ -153,10 +155,12 @@ public class ApiTagController : Controller { .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}"); + missing.AddRange(implicitTagIds.Except(implicitTags.Select(td => td.Guid))); + if(missing.Any()) + throw new ObjectNotFoundException(missing); + + if(tagDefinition!.ObjectId < 0) + throw new ApiModels.ArgumentException("Cannot update built-in tag definition"); tagDefinition.ImplicitTags.AddRange( implicitTags.ExceptBy(tagDefinition.ImplicitTags.Select(td => td.Guid), td => td.Guid)); @@ -175,15 +179,14 @@ public class ApiTagController : Controller { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); + var missing = new List<Guid>(); + 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"); + missing.Add(tagDefinitionId); implicitTagIds = implicitTagIds.Distinct().ToArray(); @@ -191,10 +194,12 @@ public class ApiTagController : Controller { .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}"); + missing.AddRange(implicitTagIds.Except(implicitTags.Select(td => td.Guid))); + if(missing.Any()) + throw new ObjectNotFoundException(missing); + + if(tagDefinition!.ObjectId < 0) + throw new ApiModels.ArgumentException("Cannot update built-in tag definition"); tagDefinition.ImplicitTags.AddRange( implicitTags.ExceptBy(tagDefinition.ImplicitTags.Select(td => td.Guid), td => td.Guid)); @@ -225,18 +230,16 @@ public class ApiTagController : Controller { .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) - return NotFound("Tag definition not found"); + throw new ObjectNotFoundException([ tagDefinitionId ]); if(tagDefinition.ObjectId < 0) - return BadRequest("Cannot update built-in tag definition"); + throw new ApiModels.ArgumentException("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 missingTags = implicitTagIds.Except(tagDefinition.ImplicitTags.Select(td => td.Guid)); + if(missingTags.Any()) + throw new ObjectNotFoundException(missingTags); var toRemove = tagDefinition.ImplicitTags .Where(td => !implicitTagIds.Contains(td.Guid)) diff --git a/Controllers/ApiUserController.cs b/Controllers/ApiUserController.cs index d678287..3230218 100644 --- a/Controllers/ApiUserController.cs +++ b/Controllers/ApiUserController.cs @@ -1,4 +1,5 @@ -using HyperBooru.Services; +using HyperBooru.ApiModels; +using HyperBooru.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -30,7 +31,10 @@ public class ApiUserController : Controller { var user = await db.Users .FirstOrDefaultAsync(u => u.Guid == userId); - return user is null ? NotFound() : Ok((ApiModels.User) user); + if(user is null) + throw new ObjectNotFoundException([ userId ]); + + return Ok((ApiModels.User) user); } [HttpPost] @@ -40,7 +44,7 @@ public class ApiUserController : Controller { using var transaction = await db.Database.BeginTransactionAsync(); if(await db.Users.AnyAsync(u => u.Username == request.Username)) - return BadRequest("Username already exists"); + throw new ApiModels.ArgumentException("Username already exists"); var user = new User() { Username = request.Username, @@ -66,17 +70,17 @@ public class ApiUserController : Controller { var user = await db.Users.FirstOrDefaultAsync(u => u.Guid == userId); if(user is null) - return NotFound(); + throw new ObjectNotFoundException([ userId ]); if(request.Username is not null) { if(string.IsNullOrWhiteSpace(request.Username)) - return BadRequest("Username cannot be empty"); + throw new ApiModels.ArgumentException("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"); + throw new ApiModels.ArgumentException("Password cannot be empty"); user.PasswordHash = UserService.HashPassword(request.Password); } @@ -89,7 +93,7 @@ public class ApiUserController : Controller { [HttpDelete("{userId}")] public async Task<IActionResult> DeleteUserAsync([FromRoute] Guid userId) { if(userId == HBContext.AdminUser) - return BadRequest("Cannot delete the admin user"); + throw new ApiModels.ArgumentException("Cannot delete the admin user"); using var db = dbFactory.CreateDbContext(); @@ -97,7 +101,7 @@ public class ApiUserController : Controller { var user = await db.Users.FirstOrDefaultAsync(u => u.Guid == userId); if(user is null) - return NotFound(); + throw new ObjectNotFoundException([ userId ]); db.Users.Remove(user); diff --git a/Controllers/MediaController.cs b/Controllers/MediaController.cs index 6a9e1fc..248765a 100644 --- a/Controllers/MediaController.cs +++ b/Controllers/MediaController.cs @@ -39,7 +39,7 @@ public class MediaController : Controller { .Include(m => m.CurrentUploadedFile) .First(m => m.Guid == mediaId); if(media is null) - return NotFound(); + throw new ObjectNotFoundException([ mediaId ]); // Check if the requested media item is a HEIC image and if it is, convert it // otherwise, return the original file content, unaltered @@ -68,14 +68,8 @@ public class MediaController : Controller { [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); - } + var thumb = mediaService.GetThumbnail(mediaId, width, height); + return new FileStreamResult(thumb, "image/jpeg"); } [HttpDelete("{mediaId}")] @@ -86,65 +80,61 @@ public class MediaController : Controller { [HttpPost] public IActionResult Upload() { if(Request.Form.Files.Count == 0) - return BadRequest("No files"); + throw new ApiModels.ArgumentException("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 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); + // 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); + 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(); + // 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); + 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); + // 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(returnMetadataParsed && returnMetadata) + return Content(media.Guid.ToString()); } if(Request.Form.Files.Count == 1) diff --git a/ExceptionMiddleware.cs b/ExceptionMiddleware.cs index 29d0e10..ba4049a 100644 --- a/ExceptionMiddleware.cs +++ b/ExceptionMiddleware.cs @@ -23,16 +23,16 @@ public sealed class ExceptionMiddleware { e.GetType().GetCustomAttribute<ExceptionStatusCodeAttribute>()?.StatusCode ?? StatusCodes.Status500InternalServerError; - await context.Response.WriteAsJsonAsync(e); + context.Response.Clear(); - var x = 1; + await context.Response.WriteAsJsonAsync(e); } catch(Exception) { context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; context.Response.Clear(); - await context.Response.WriteAsync(string.Empty); + await context.Response.WriteAsJsonAsync(new ServerException()); } } } diff --git a/HBContext.cs b/HBContext.cs index b684a51..766c0a3 100644 --- a/HBContext.cs +++ b/HBContext.cs @@ -1,4 +1,5 @@ -using HyperBooru.Services; +using HyperBooru.ApiModels; +using HyperBooru.Services; using Microsoft.EntityFrameworkCore; namespace HyperBooru; diff --git a/Pages/Component/MediaTagTable.razor b/Pages/Component/MediaTagTable.razor index e687529..1b62832 100644 --- a/Pages/Component/MediaTagTable.razor +++ b/Pages/Component/MediaTagTable.razor @@ -61,7 +61,7 @@ var media = db.Media.First(m => m.ObjectId == Media.ObjectId); tagDefs = tagService.GetAllTags(Media) - .Where(e => e.tagDefinition.Source == TagSource.UserTag) + .Where(e => e.tagDefinition.Source == ApiModels.TagSource.UserTag) .ToArray(); } diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor index 87065d7..d33b178 100644 --- a/Pages/Component/TagSelectDialog.razor +++ b/Pages/Component/TagSelectDialog.razor @@ -97,7 +97,7 @@ tagDefinitions = db.TagDefinitions .Include(td => td.ImplicitTags) - .Where(td => td.Source == TagSource.UserTag) + .Where(td => td.Source == ApiModels.TagSource.UserTag) .OrderBy(td => td.Name) .AsEnumerable() .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor index f3dca0f..5d02e03 100644 --- a/Pages/TagDefinitions.razor +++ b/Pages/TagDefinitions.razor @@ -34,9 +34,9 @@ <i> @{ var implicitTags = tagDef.ImplicitTags - .Where(td => td.Source == TagSource.UserTag); + .Where(td => td.Source == ApiModels.TagSource.UserTag); foreach(var tag in implicitTags) { - <a href="/Gallery?q=@tag.Name" class="nondecorated"> + <a href="/Gallery?t=@tag.Guid" class="nondecorated"> @tag.Name </a> if(tag != implicitTags.Last()) @@ -108,7 +108,7 @@ tagDefinitions = dbFactory.CreateDbContext().TagDefinitions .Include(td => td.ImplicitTags) - .Where(td => td.Source == TagSource.UserTag) + .Where(td => td.Source == ApiModels.TagSource.UserTag) .OrderBy(td => td.Namespace) .ThenBy(td => td.Name) .AsEnumerable() diff --git a/Server.csproj b/Server.csproj index 45bb9bd..9d60d7d 100644 --- a/Server.csproj +++ b/Server.csproj @@ -6,9 +6,9 @@ <ImplicitUsings>enable</ImplicitUsings> <AssemblyName>HyperBooru</AssemblyName> <RootNamespace>HyperBooru</RootNamespace> - <AssemblyVersion>0.17.0.0</AssemblyVersion> + <AssemblyVersion>0.18.0.0</AssemblyVersion> <FileVersion>$(AssemblyVersion)</FileVersion> - <Version>0.17-alpha</Version> + <Version>0.18-alpha</Version> <UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId> </PropertyGroup> @@ -37,16 +37,16 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.13.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7"> + <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.14.0" /> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.8" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Mime-Detective" Version="25.8.1" /> - <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" /> - <PackageReference Include="System.Drawing.Common" Version="10.0.7" /> + <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.1" /> + <PackageReference Include="System.Drawing.Common" Version="10.0.8" /> <PackageReference Include="Tesseract" Version="5.2.0" /> </ItemGroup> diff --git a/Services/MediaService.cs b/Services/MediaService.cs index e497570..2d1533c 100644 --- a/Services/MediaService.cs +++ b/Services/MediaService.cs @@ -304,7 +304,7 @@ public class MediaService : IMediaService { return System.IO.File.OpenRead(thumbPath); if(!File.Exists(GetPath(mediaId))) - throw new ObjectNotFoundException(mediaId); + throw new ObjectNotFoundException([ mediaId ]); using var image = new MagickImage(GetPath(mediaId)); @@ -329,7 +329,7 @@ public class MediaService : IMediaService { return System.IO.File.OpenRead(convertedPath); if(!File.Exists(GetPath(mediaId))) - throw new ObjectNotFoundException(mediaId); + throw new ObjectNotFoundException([ mediaId ]); using var image = new MagickImage(GetPath(mediaId)); image.Write(convertedPath, format); @@ -1,12 +1,8 @@ -using System.ComponentModel.DataAnnotations.Schema; +using HyperBooru.ApiModels; +using System.ComponentModel.DataAnnotations.Schema; namespace HyperBooru; -public enum TagSource { - Internal, - UserTag -} - public class TagDefinition : HBObject { public TagSource Source { get; set; } = TagSource.Internal; public string? Namespace { get; set; } @@ -16,6 +12,7 @@ public class TagDefinition : HBObject { public static explicit operator ApiModels.TagDefinition(TagDefinition tagDefinition) => new() { TagDefinitionId = tagDefinition.Guid, + Source = tagDefinition.Source, Namespace = tagDefinition.Namespace, Name = tagDefinition.Name, Alias = tagDefinition.Alias, @@ -1,3 +1,7 @@ +# WASM + - [ ] Sanitize controller inputs for empty strings + - [ ] Audit and fix namespaces (e.g. HyperBooru.Controllers -> HyperBooru.Server.Controllers) + # Bugs - [X] Images in the gallery on mobile can easily exceed screen width - [X] Images smaller than the requested thumbnail size aren't delivered @@ -5,6 +5,11 @@ public static class Extensions { "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q" }; + public static string? NullIfEmpty(this string s) { + s = s.Trim(); + return string.IsNullOrEmpty(s) ? null : s; + } + public static DateTime? TryParseDateTimeUtc(this string s) { bool success = DateTime.TryParse(s, out var dateTime); return success ? DateTime.SpecifyKind(dateTime, DateTimeKind.Utc) : null; |
