summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2026-06-21 21:02:44 +1000
committerJake Mannens <jake@asger.xyz>2026-06-25 12:26:07 +1000
commit713867bbda8322a0938d6b8f0c850752622fdd42 (patch)
tree1f8c4e58480471aecdee37e3f734c4c1211e16b1
parent79d383f6f48df0078e6ba13239f9c4c94fbc9ea6 (diff)
-rw-r--r--Controllers/ApiMediaController.cs24
-rw-r--r--Controllers/ApiUserController.cs2
-rw-r--r--HBContext.cs15
-rw-r--r--Pages/Component/AboutDialog.razor2
-rw-r--r--Pages/Component/TagSelectDialog.razor2
-rw-r--r--Pages/TagDefinitions.razor8
-rw-r--r--Server.csproj4
-rw-r--r--Services/FeedService.cs2
-rw-r--r--Services/MediaService.cs33
9 files changed, 60 insertions, 32 deletions
diff --git a/Controllers/ApiMediaController.cs b/Controllers/ApiMediaController.cs
index bb6c81e..8ca6121 100644
--- a/Controllers/ApiMediaController.cs
+++ b/Controllers/ApiMediaController.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
+using System.Text.RegularExpressions;
namespace HyperBooru.Controllers;
@@ -15,6 +16,9 @@ public class ApiMediaController : Controller {
private IDbContextFactory<HBContext> dbFactory;
private IMediaService mediaService;
+ private readonly Regex checksumRegex =
+ new Regex("^[0-9a-f]{32}$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
+
public ApiMediaController(IDbContextFactory<HBContext> dbFactory, IMediaService mediaService) {
this.dbFactory = dbFactory;
this.mediaService = mediaService;
@@ -32,6 +36,26 @@ public class ApiMediaController : Controller {
return Ok((ApiModels.Media) media);
}
+ [HttpGet("checksum/{checksum}")]
+ public async Task<IActionResult> GetChecksum([FromRoute] string checksum) {
+ if(!checksumRegex.IsMatch(checksum))
+ throw new ApiModels.ArgumentException("Invalid checksum string");
+
+ using var db = dbFactory.CreateDbContext();
+
+ var media = await db.UploadedFiles
+ .Include(uf => uf.Media)
+ .Where(uf => uf.Checksum == checksum.ToLower())
+ .Select(uf => uf.Media)
+ .Distinct()
+ .ToArrayAsync();
+
+ if(media.Count() == 0)
+ throw new ObjectNotFoundException([ Guid.Empty ]);
+
+ return Ok(media.Select(m => (ApiModels.Media) m).ToArray());
+ }
+
[HttpGet("{mediaId}/files")]
public async Task<IActionResult> GetUploadedFiles([FromRoute] Guid mediaId) {
using var db = dbFactory.CreateDbContext();
diff --git a/Controllers/ApiUserController.cs b/Controllers/ApiUserController.cs
index 3230218..c581956 100644
--- a/Controllers/ApiUserController.cs
+++ b/Controllers/ApiUserController.cs
@@ -92,7 +92,7 @@ public class ApiUserController : Controller {
[HttpDelete("{userId}")]
public async Task<IActionResult> DeleteUserAsync([FromRoute] Guid userId) {
- if(userId == HBContext.AdminUser)
+ if(userId == InternalTag.AdminUser)
throw new ApiModels.ArgumentException("Cannot delete the admin user");
using var db = dbFactory.CreateDbContext();
diff --git a/HBContext.cs b/HBContext.cs
index 766c0a3..d13378d 100644
--- a/HBContext.cs
+++ b/HBContext.cs
@@ -4,16 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace HyperBooru;
-enum HBObjectId {
- NsfwTag = -1,
- IngestTag = -2,
- AdminUser = -3
-}
-
public class HBContext : DbContext {
- public static readonly Guid NsfwTag = new("EBDAD4F8-455A-4351-8017-1D4854D6FA38");
- public static readonly Guid IngestTag = new("EA212801-5BCC-4C0E-814F-FB9D30DB58BC");
- public static readonly Guid AdminUser = new("4FA948F4-7C45-4F81-BB6B-E417491E6C96");
public DbSet<HBObject> Objects { get; set; }
public DbSet<User> Users { get; set; }
@@ -49,13 +40,13 @@ public class HBContext : DbContext {
modelBuilder.Entity<TagDefinition>().HasData(new TagDefinition[] {
new() {
ObjectId = (int) HBObjectId.NsfwTag,
- Guid = NsfwTag,
+ Guid = InternalTag.NsfwTag,
Source = TagSource.Internal,
Name = "nsfw"
},
new() {
ObjectId = (int) HBObjectId.IngestTag,
- Guid = IngestTag,
+ Guid = InternalTag.IngestTag,
Source = TagSource.Internal,
Name = "ingest"
}
@@ -65,7 +56,7 @@ public class HBContext : DbContext {
modelBuilder.Entity<User>().HasData(new User[] {
new() {
ObjectId = (int) HBObjectId.AdminUser,
- Guid = AdminUser,
+ Guid = InternalTag.AdminUser,
Username = "admin",
PasswordHash = UserService.HashPassword("admin")
}
diff --git a/Pages/Component/AboutDialog.razor b/Pages/Component/AboutDialog.razor
index 1229dc7..d1082bc 100644
--- a/Pages/Component/AboutDialog.razor
+++ b/Pages/Component/AboutDialog.razor
@@ -702,7 +702,7 @@
using var db = dbFactory.CreateDbContext();
progress = (
Untagged: db.Media
- .Where(m => m.Tags.Any(t => t.TagDefinition.ObjectId == (int) HBObjectId.IngestTag))
+ .Where(m => m.Tags.Any(t => t.TagDefinition.ObjectId == (int) ApiModels.HBObjectId.IngestTag))
.Count(),
Total: db.Media.Count()
);
diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor
index d33b178..26a35a2 100644
--- a/Pages/Component/TagSelectDialog.razor
+++ b/Pages/Component/TagSelectDialog.razor
@@ -91,7 +91,7 @@
int[] nsfwTags = Array.Empty<int>();
if(!userService.UserSessionState.ShowNsfw)
- nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
+ nsfwTags = tagService.TagsThatImply(ApiModels.InternalTag.NsfwTag)
.Select(td => td.ObjectId)
.ToArray();
diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor
index 5d02e03..c86fc81 100644
--- a/Pages/TagDefinitions.razor
+++ b/Pages/TagDefinitions.razor
@@ -53,7 +53,7 @@
<a href="javascript:;" @onclick=@(() => PromptImplicitTags(tagDef))>
Implicit Tags
</a>
- @if(tagDef.ImplicitTags.Select(td => td.Guid).Contains(HBContext.NsfwTag)) {
+ @if(tagDef.ImplicitTags.Select(td => td.Guid).Contains(ApiModels.InternalTag.NsfwTag)) {
<a href="javascript:;" @onclick=@(() => SetNsfw(tagDef, false))>Make SFW</a>
} else {
<a href="javascript:;" @onclick=@(() => SetNsfw(tagDef, true))>Make NSFW</a>
@@ -102,7 +102,7 @@
private void LoadTags() {
int[] nsfwTags = Array.Empty<int>();
if(!userService.UserSessionState.ShowNsfw)
- nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
+ nsfwTags = tagService.TagsThatImply(ApiModels.InternalTag.NsfwTag)
.Select(td => td.ObjectId)
.ToArray();
@@ -169,9 +169,9 @@
private void SetNsfw(TagDefinition tagDef, bool nsfw) {
if(nsfw)
- tagService.AddImplicitTag(tagDef.Guid, HBContext.NsfwTag);
+ tagService.AddImplicitTag(tagDef.Guid, ApiModels.InternalTag.NsfwTag);
else
- tagService.RemoveImplicitTag(tagDef.Guid, HBContext.NsfwTag);
+ tagService.RemoveImplicitTag(tagDef.Guid, ApiModels.InternalTag.NsfwTag);
LoadTags();
StateHasChanged();
}
diff --git a/Server.csproj b/Server.csproj
index 8de0960..1099c7a 100644
--- a/Server.csproj
+++ b/Server.csproj
@@ -6,9 +6,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>HyperBooru</AssemblyName>
<RootNamespace>HyperBooru</RootNamespace>
- <AssemblyVersion>0.19.0.0</AssemblyVersion>
+ <AssemblyVersion>0.20.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
- <Version>0.19-alpha</Version>
+ <Version>0.20-alpha</Version>
<UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId>
</PropertyGroup>
diff --git a/Services/FeedService.cs b/Services/FeedService.cs
index 3744e73..112b3c0 100644
--- a/Services/FeedService.cs
+++ b/Services/FeedService.cs
@@ -116,7 +116,7 @@ public class FeedService : IFeedService {
if(!includeNsfw)
media = media
- .Where(m => !TagsThatImply(db, HBContext.NsfwTag)
+ .Where(m => !TagsThatImply(db, InternalTag.NsfwTag)
.Intersect(m.Tags.Select(t => t.TagDefinitionId))
.Any());
diff --git a/Services/MediaService.cs b/Services/MediaService.cs
index a0521db..9974802 100644
--- a/Services/MediaService.cs
+++ b/Services/MediaService.cs
@@ -4,7 +4,6 @@ using ImageMagick;
using Microsoft.EntityFrameworkCore;
using MimeDetective;
using MimeDetective.Definitions;
-using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
@@ -78,13 +77,13 @@ public class MediaService : IMediaService {
.ThenInclude(t => t.TagDefinition)
.First(m => m.Guid == media.Guid);
var ingestTag = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
+ .First(td => td.Guid == InternalTag.IngestTag);
if(ingest) {
- if(!media.Tags.Select(t => t.TagDefinition.Guid).Contains(HBContext.IngestTag))
+ if(!media.Tags.Select(t => t.TagDefinition.Guid).Contains(InternalTag.IngestTag))
media.Tags.Add(new(ingestTag));
} else {
- media.Tags.RemoveAll(t => t.TagDefinition.Guid == HBContext.IngestTag);
+ media.Tags.RemoveAll(t => t.TagDefinition.Guid == InternalTag.IngestTag);
}
db.SaveChanges();
@@ -190,7 +189,7 @@ public class MediaService : IMediaService {
if(media is null) {
var ingestTagDef = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
+ .First(td => td.Guid == InternalTag.IngestTag);
media = new() {
UploadedFiles = new() {
@@ -299,23 +298,37 @@ public class MediaService : IMediaService {
public Stream GetThumbnail(Guid mediaId, int? width, int? height) {
if(width is null && height is null)
- throw new ThumbnailException(
- "Both width and height cannot be null!",
- mediaId);
+ throw new ApiModels.ArgumentException("Both width and height cannot be null!");
var thumbPath = GetThumbnailPath(mediaId, width, height);
if(File.Exists(thumbPath))
- return System.IO.File.OpenRead(thumbPath);
+ return File.OpenRead(thumbPath);
if(!File.Exists(GetPath(mediaId)))
throw new ObjectNotFoundException([ mediaId ]);
using var image = new MagickImage(GetPath(mediaId));
- if(width > image.Width || height > image.Height) {
+ if(width > image.Width)
width = (int) image.Width;
+ if(height > image.Height)
height = (int) image.Height;
+
+ // Crop the image if a different aspect ratio is specified
+ if(width is not null && height is not null) {
+ double requestedRatio = (double) width / (double) height;
+ double currentRatio = (double) image.Width / (double) image.Height;
+
+ double w = image.Width;
+ double h = image.Height;
+
+ if(requestedRatio < currentRatio)
+ w = w / currentRatio * requestedRatio;
+ if(requestedRatio > currentRatio)
+ h = h / requestedRatio * currentRatio;
+
+ image.Extent((uint) w, (uint) h, Gravity.Center);
}
image.Thumbnail((uint) (width ?? -1), (uint) (height ?? -1));