summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Controllers/ApiFeedController.cs2
-rw-r--r--Controllers/ApiMediaController.cs50
-rw-r--r--Controllers/ApiStatisticsController.cs26
-rw-r--r--Controllers/ApiTagController.cs81
-rw-r--r--Controllers/ApiUserController.cs20
-rw-r--r--Controllers/MediaController.cs106
-rw-r--r--ExceptionMiddleware.cs6
-rw-r--r--HBContext.cs3
-rw-r--r--Pages/Component/MediaTagTable.razor2
-rw-r--r--Pages/Component/TagSelectDialog.razor2
-rw-r--r--Pages/TagDefinitions.razor6
-rw-r--r--Server.csproj16
-rw-r--r--Services/MediaService.cs4
-rw-r--r--Tag.cs9
-rw-r--r--Todo.md4
-rw-r--r--Util.cs5
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);
diff --git a/Tag.cs b/Tag.cs
index c857c66..7da6232 100644
--- a/Tag.cs
+++ b/Tag.cs
@@ -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,
diff --git a/Todo.md b/Todo.md
index 23c406d..612f60e 100644
--- a/Todo.md
+++ b/Todo.md
@@ -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
diff --git a/Util.cs b/Util.cs
index 6af6c81..7de2b6e 100644
--- a/Util.cs
+++ b/Util.cs
@@ -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;