summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2026-05-16 18:35:15 +1000
committerJake Mannens <jake@asger.xyz>2026-05-16 18:35:15 +1000
commitdf4bfad33e1b3fc2ce35af8000380029ecb444cf (patch)
tree65ac6d8644528c7aaeee992671cf24fe13486098
parentc2b3c05f027d315f4e553ff656d1f06e99a82488 (diff)
-rw-r--r--Controllers/ApiTagController.cs217
-rw-r--r--Pages/ViewMedia.razor.css6
-rw-r--r--Server.csproj9
3 files changed, 229 insertions, 3 deletions
diff --git a/Controllers/ApiTagController.cs b/Controllers/ApiTagController.cs
index 26e3dc4..f48cc05 100644
--- a/Controllers/ApiTagController.cs
+++ b/Controllers/ApiTagController.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Authorization;
+using HyperBooru.ApiModels;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -35,4 +36,218 @@ public class ApiTagController : Controller {
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/Pages/ViewMedia.razor.css b/Pages/ViewMedia.razor.css
index 60b04be..1a856d3 100644
--- a/Pages/ViewMedia.razor.css
+++ b/Pages/ViewMedia.razor.css
@@ -25,6 +25,12 @@ div#image-container {
width: 100%;
}
+@media (hover: none) and (pointer: coarse) {
+ div#image-container {
+ padding: 0 !important;
+ }
+}
+
div#image-container > img {
display: block;
left: 50%;
diff --git a/Server.csproj b/Server.csproj
index aeccae2..45bb9bd 100644
--- a/Server.csproj
+++ b/Server.csproj
@@ -6,9 +6,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>HyperBooru</AssemblyName>
<RootNamespace>HyperBooru</RootNamespace>
- <AssemblyVersion>0.16.0.0</AssemblyVersion>
+ <AssemblyVersion>0.17.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
- <Version>0.16-alpha</Version>
+ <Version>0.17-alpha</Version>
<UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId>
</PropertyGroup>
@@ -24,6 +24,11 @@
<DefaultItemExcludes>$(DefaultItemExcludes);Data/**</DefaultItemExcludes>
</PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)' == 'Release'">
+ <DebugType>None</DebugType>
+ <DebugSymbols>false</DebugSymbols>
+ </PropertyGroup>
+
<ItemGroup>
<Compile Remove="Data/**" />
<Content Remove="Data/**" />