From 81a0570c1b64891f286ee86d34d6f77090d525e3 Mon Sep 17 00:00:00 2001 From: Jake Mannens Date: Fri, 5 Jun 2026 00:37:02 +1000 Subject: Deleted server-specific files --- .config/dotnet-tools.json | 13 - App.razor | 19 - Controllers/ApiFeedController.cs | 25 -- Controllers/ApiMediaController.cs | 227 ------------ Controllers/ApiStatisticsController.cs | 26 -- Controllers/ApiTagController.cs | 256 ------------- Controllers/ApiUserController.cs | 113 ------ Controllers/LoginController.cs | 49 --- Controllers/MediaController.cs | 145 -------- Dockerfile | 16 - ExceptionMiddleware.cs | 64 ---- HBContext.cs | 85 ----- HBObject.cs | 14 - Media.cs | 92 ----- .../20260131125650_InitialMigration.Designer.cs | 362 ------------------- Migrations/20260131125650_InitialMigration.cs | 319 ---------------- Migrations/HBContextModelSnapshot.cs | 359 ------------------ Pages/_Host.cshtml | 37 -- Properties/launchSettings.json | 31 -- Services/ConfigService.cs | 72 ---- Services/FeedService.cs | 212 ----------- Services/MediaService.cs | 400 --------------------- Services/OcrService.cs | 128 ------- Services/TagService.cs | 305 ---------------- Services/UserService.cs | 76 ---- Tag.cs | 34 -- Todo.md | 45 --- User.cs | 15 - tessdata/eng.traineddata | Bin 15400601 -> 0 bytes wwwroot/css/site.css | 28 -- wwwroot/favicon.ico | Bin 3262 -> 0 bytes wwwroot/icon-192.png | Bin 31523 -> 0 bytes wwwroot/icon-512.png | Bin 136487 -> 0 bytes wwwroot/images/book.svg | 7 - wwwroot/images/checkmark.svg | 7 - wwwroot/images/cross.svg | 7 - wwwroot/images/edit.svg | 7 - wwwroot/images/info.svg | 63 ---- wwwroot/images/loginbg.webp | Bin 2247672 -> 0 bytes wwwroot/images/photo.svg | 7 - wwwroot/images/tag.svg | 7 - wwwroot/images/trash.svg | 7 - wwwroot/js/dialog.js | 78 ---- wwwroot/js/keyboard.js | 57 --- wwwroot/js/mobile.js | 7 - wwwroot/loginbg.webm | Bin 390877 -> 0 bytes wwwroot/manifest.webmanifest | 6 - wwwroot/styles/data-table.css | 21 -- wwwroot/styles/global.css | 214 ----------- 49 files changed, 4062 deletions(-) delete mode 100644 .config/dotnet-tools.json delete mode 100644 App.razor delete mode 100644 Controllers/ApiFeedController.cs delete mode 100644 Controllers/ApiMediaController.cs delete mode 100644 Controllers/ApiStatisticsController.cs delete mode 100644 Controllers/ApiTagController.cs delete mode 100644 Controllers/ApiUserController.cs delete mode 100644 Controllers/LoginController.cs delete mode 100644 Controllers/MediaController.cs delete mode 100644 Dockerfile delete mode 100644 ExceptionMiddleware.cs delete mode 100644 HBContext.cs delete mode 100644 HBObject.cs delete mode 100644 Media.cs delete mode 100644 Migrations/20260131125650_InitialMigration.Designer.cs delete mode 100644 Migrations/20260131125650_InitialMigration.cs delete mode 100644 Migrations/HBContextModelSnapshot.cs delete mode 100644 Pages/_Host.cshtml delete mode 100644 Properties/launchSettings.json delete mode 100644 Services/ConfigService.cs delete mode 100644 Services/FeedService.cs delete mode 100644 Services/MediaService.cs delete mode 100644 Services/OcrService.cs delete mode 100644 Services/TagService.cs delete mode 100644 Services/UserService.cs delete mode 100644 Tag.cs delete mode 100644 Todo.md delete mode 100644 User.cs delete mode 100644 tessdata/eng.traineddata delete mode 100644 wwwroot/css/site.css delete mode 100644 wwwroot/favicon.ico delete mode 100644 wwwroot/icon-192.png delete mode 100644 wwwroot/icon-512.png delete mode 100644 wwwroot/images/book.svg delete mode 100644 wwwroot/images/checkmark.svg delete mode 100644 wwwroot/images/cross.svg delete mode 100644 wwwroot/images/edit.svg delete mode 100644 wwwroot/images/info.svg delete mode 100644 wwwroot/images/loginbg.webp delete mode 100644 wwwroot/images/photo.svg delete mode 100644 wwwroot/images/tag.svg delete mode 100644 wwwroot/images/trash.svg delete mode 100644 wwwroot/js/dialog.js delete mode 100644 wwwroot/js/keyboard.js delete mode 100644 wwwroot/js/mobile.js delete mode 100644 wwwroot/loginbg.webm delete mode 100644 wwwroot/manifest.webmanifest delete mode 100644 wwwroot/styles/data-table.css delete mode 100644 wwwroot/styles/global.css diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json deleted file mode 100644 index e3d2b85..0000000 --- a/.config/dotnet-tools.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-ef": { - "version": "8.0.23", - "commands": [ - "dotnet-ef" - ], - "rollForward": false - } - } -} \ No newline at end of file diff --git a/App.razor b/App.razor deleted file mode 100644 index b4e47c9..0000000 --- a/App.razor +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
-
diff --git a/Controllers/ApiFeedController.cs b/Controllers/ApiFeedController.cs deleted file mode 100644 index 068cc17..0000000 --- a/Controllers/ApiFeedController.cs +++ /dev/null @@ -1,25 +0,0 @@ -using HyperBooru.ApiModels; -using HyperBooru.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru.Controllers; - -[ApiController] -[Authorize] -[Route("/api/feed")] -public class ApiFeedController : Controller { - private IFeedService feedService; - - public ApiFeedController(IDbContextFactory dbFactory, IFeedService feedService) => - this.feedService = feedService; - - [HttpPost] - public IActionResult FetchChunkAsync([FromBody] FeedRequest feedRequest) { - if(feedRequest.Count > 1000) - 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 deleted file mode 100644 index bb6c81e..0000000 --- a/Controllers/ApiMediaController.cs +++ /dev/null @@ -1,227 +0,0 @@ -using HyperBooru.ApiModels; -using HyperBooru.Services; -using HyperBooru.Util; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Text.Json; - -namespace HyperBooru.Controllers; - -[ApiController] -[Authorize] -[Route("/api/media")] -public class ApiMediaController : Controller { - private IDbContextFactory dbFactory; - private IMediaService mediaService; - - public ApiMediaController(IDbContextFactory dbFactory, IMediaService mediaService) { - this.dbFactory = dbFactory; - this.mediaService = mediaService; - } - - [HttpGet("{mediaId}")] - public async Task 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 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 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) - throw new ObjectNotFoundException([ updatedMedia.MediaId ]); - - media.ShortDescription = updatedMedia.ShortDescription?.NullIfEmpty(); - media.LongDescription = updatedMedia.LongDescription?.NullIfEmpty(); - - await db.SaveChangesAsync(); - await transaction.CommitAsync(); - - return Ok(); - } - - [HttpPost] - public IActionResult Upload() { - if(Request.Form.Files.Count == 0) - throw new ApiModels.ArgumentException("No files"); - if(Request.Form.Files.Count > 1) - throw new ApiModels.ArgumentException("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(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.NullIfEmpty(), - 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 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) - throw new ObjectNotFoundException([ mediaId ]); - - return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); - } - - [HttpPatch("{mediaId}/tags")] - public async Task AddTagsToMediaAsync( - [FromRoute] Guid mediaId, - [FromBody] Guid[] tagIds) { - - using var db = dbFactory.CreateDbContext(); - using var transaction = await db.Database.BeginTransactionAsync(); - - var missing = new List(); - - 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) - missing.Add(mediaId); - - tagIds = tagIds.Distinct().ToArray(); - - var tags = await db.TagDefinitions - .Where(td => tagIds.Contains(td.Guid)) - .ToArrayAsync(); - - 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)) - .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 ReplaceMediaTagsAsync( - [FromRoute] Guid mediaId, - [FromBody] Guid[] tagIds) { - - using var db = dbFactory.CreateDbContext(); - using var transaction = await db.Database.BeginTransactionAsync(); - - var missing = new List(); - - 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) - missing.Add(mediaId); - - tagIds = tagIds.Distinct().Order().ToArray(); - var tags = await db.TagDefinitions - .Where(td => tagIds.Contains(td.Guid)) - .ToArrayAsync(); - - 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)) - .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 DeleteTagsFromMediaAsync( - [FromRoute] Guid mediaId, - [FromBody] Guid[] tagIds) { - - using var db = dbFactory.CreateDbContext(); - using var transaction = await db.Database.BeginTransactionAsync(); - - var missing = new List(); - - 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) - missing.Add(mediaId); - - tagIds = tagIds.Distinct().Order().ToArray(); - - 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))); - - await db.SaveChangesAsync(); - await transaction.CommitAsync(); - - return Ok(media.Tags.Select(t => (ApiModels.TagDefinition) t.TagDefinition).ToArray()); - } -} diff --git a/Controllers/ApiStatisticsController.cs b/Controllers/ApiStatisticsController.cs deleted file mode 100644 index 3acd1d5..0000000 --- a/Controllers/ApiStatisticsController.cs +++ /dev/null @@ -1,26 +0,0 @@ -using HyperBooru.ApiModels; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru.Controllers; - -[ApiController] -[Route("/api/stats")] -public class ApiStatisticsController : Controller { - private IDbContextFactory dbFactory; - - public ApiStatisticsController(IDbContextFactory dbFactory) => - this.dbFactory = dbFactory; - - [HttpGet("ingest")] - public async Task 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 deleted file mode 100644 index d1e49ee..0000000 --- a/Controllers/ApiTagController.cs +++ /dev/null @@ -1,256 +0,0 @@ -using HyperBooru.ApiModels; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru.Controllers; - -[ApiController] -[Authorize] -[Route("/api/tag")] -public class ApiTagController : Controller { - private IDbContextFactory dbFactory; - - public ApiTagController(IDbContextFactory dbFactory) => - this.dbFactory = dbFactory; - - [HttpGet("definition")] - public async Task 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 GetTagDefinitionAsync([FromRoute] Guid tagDefinitionId) { - using var db = dbFactory.CreateDbContext(); - - var tagDefinition = await db.TagDefinitions - .Include(td => td.ImplicitTags) - .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); - - if(tagDefinition is null) - throw new ObjectNotFoundException([ tagDefinitionId ]); - - return Ok(tagDefinition); - } - - [HttpPost("definition")] - public async Task CreateTagDefinitionAsync([FromBody] TagCreateRequest request) { - using var db = dbFactory.CreateDbContext(); - using var transaction = await db.Database.BeginTransactionAsync(); - - 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(nameExists || aliasExists) - throw new TagDuplicateException(nameExists, aliasExists); - - List 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 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) - throw new ObjectNotFoundException([ tagDefinitionId ]); - - if(tagDefinition.ObjectId < 0) - throw new ApiModels.ArgumentException("Cannot delete built-in tag definition"); - - db.TagDefinitions.Remove(tagDefinition); - - await db.SaveChangesAsync(); - await transaction.CommitAsync(); - - return Ok(); - } - - [HttpPatch("definition/{tagDefinitionId}")] - public async Task 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) - throw new ObjectNotFoundException([ tagDefinitionId ]); - - if(tagDefinition.ObjectId < 0) - throw new ApiModels.ArgumentException("Cannot update built-in tag definition"); - - 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(nameExists || aliasExists) - throw new TagDuplicateException(nameExists, aliasExists); - - 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 AddImplicitTagsAsync( - [FromRoute] Guid tagDefinitionId, - [FromBody] Guid[] implicitTagIds) { - - using var db = dbFactory.CreateDbContext(); - using var transaction = await db.Database.BeginTransactionAsync(); - - var missing = new List(); - - var tagDefinition = await db.TagDefinitions - .Include(td => td.ImplicitTags) - .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); - - if(tagDefinition is null) - missing.Add(tagDefinitionId); - - implicitTagIds = implicitTagIds.Distinct().ToArray(); - - var implicitTags = db.TagDefinitions - .Where(td => implicitTagIds.Contains(td.Guid)) - .ToArray(); - - 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)); - - await db.SaveChangesAsync(); - await transaction.CommitAsync(); - - return Ok(); - } - - [HttpPut("definition/{tagDefinitionId}/implicit")] - public async Task ReplaceImplicitTagsAsync( - [FromRoute] Guid tagDefinitionId, - [FromBody] Guid[] implicitTagIds) { - - using var db = dbFactory.CreateDbContext(); - using var transaction = await db.Database.BeginTransactionAsync(); - - var missing = new List(); - - var tagDefinition = await db.TagDefinitions - .Include(td => td.ImplicitTags) - .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); - - if(tagDefinition is null) - missing.Add(tagDefinitionId); - - implicitTagIds = implicitTagIds.Distinct().ToArray(); - - var implicitTags = db.TagDefinitions - .Where(td => implicitTagIds.Contains(td.Guid)) - .ToArray(); - - 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)); - - 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 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) - throw new ObjectNotFoundException([ tagDefinitionId ]); - - if(tagDefinition.ObjectId < 0) - throw new ApiModels.ArgumentException("Cannot update built-in tag definition"); - - implicitTagIds = implicitTagIds.Distinct().ToArray(); - - 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)) - .ToArray(); - - foreach(var td in toRemove) - tagDefinition.ImplicitTags.Remove(td); - - await db.SaveChangesAsync(); - await transaction.CommitAsync(); - - return Ok(); - } -} diff --git a/Controllers/ApiUserController.cs b/Controllers/ApiUserController.cs deleted file mode 100644 index 3230218..0000000 --- a/Controllers/ApiUserController.cs +++ /dev/null @@ -1,113 +0,0 @@ -using HyperBooru.ApiModels; -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 dbFactory; - - public ApiUserController(IDbContextFactory dbFactory) => - this.dbFactory = dbFactory; - - [HttpGet] - public async Task GetAllUsersAsync() { - using var db = dbFactory.CreateDbContext(); - - return Ok(await db.Users - .Select(u => (ApiModels.User) u) - .ToArrayAsync()); - } - - [HttpGet("{userId}")] - public async Task GetUserAsync([FromRoute] Guid userId) { - using var db = dbFactory.CreateDbContext(); - - var user = await db.Users - .FirstOrDefaultAsync(u => u.Guid == userId); - - if(user is null) - throw new ObjectNotFoundException([ userId ]); - - return Ok((ApiModels.User) user); - } - - [HttpPost] - public async Task 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)) - throw new ApiModels.ArgumentException("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 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) - throw new ObjectNotFoundException([ userId ]); - - if(request.Username is not null) { - if(string.IsNullOrWhiteSpace(request.Username)) - throw new ApiModels.ArgumentException("Username cannot be empty"); - user.Username = request.Username; - } - - if(request.Password is not null) { - if(string.IsNullOrWhiteSpace(request.Password)) - throw new ApiModels.ArgumentException("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 DeleteUserAsync([FromRoute] Guid userId) { - if(userId == HBContext.AdminUser) - throw new ApiModels.ArgumentException("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) - throw new ObjectNotFoundException([ userId ]); - - db.Users.Remove(user); - - await db.SaveChangesAsync(); - await transaction.CommitAsync(); - - return Ok((ApiModels.User) user); - } -} diff --git a/Controllers/LoginController.cs b/Controllers/LoginController.cs deleted file mode 100644 index c93f0d5..0000000 --- a/Controllers/LoginController.cs +++ /dev/null @@ -1,49 +0,0 @@ -using HyperBooru.Services; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; - -namespace HyperBooru.Controllers; - -[ApiController] -[Route("/")] -public class LoginController : Controller { - private IHttpContextAccessor httpContextAccessor; - - public LoginController(IHttpContextAccessor httpContextAccessor) => - this.httpContextAccessor = httpContextAccessor; - - [HttpPost("Login")] - public async Task Login( - [FromForm] string username, - [FromForm] string password, - HBContext db) { - - var user = db.Users.FirstOrDefault(u => u.Username == username); - if(user is null) - return StatusCode(403); - - var hash = UserService.HashPassword(password); - if(hash != user.PasswordHash) - return StatusCode(403); - - var claims = new Claim[] { - new Claim(ClaimTypes.Name, user.Username), - new Claim("ObjectId", user.ObjectId.ToString()) - }; - - var claimsIdentity = new ClaimsIdentity( - claims, - CookieAuthenticationDefaults.AuthenticationScheme); - - var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); - - await httpContextAccessor.HttpContext!.SignInAsync(claimsPrincipal); - return Ok(); - } - - [HttpPost("Logout")] - public async Task Logout() => - await httpContextAccessor.HttpContext!.SignOutAsync(); -} diff --git a/Controllers/MediaController.cs b/Controllers/MediaController.cs deleted file mode 100644 index 248765a..0000000 --- a/Controllers/MediaController.cs +++ /dev/null @@ -1,145 +0,0 @@ -using HyperBooru.ApiModels; -using HyperBooru.Services; -using HyperBooru.Util; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru.Controllers; - -[ApiController] -[Authorize] -[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) - 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 - 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(); - - 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) { - - var thumb = mediaService.GetThumbnail(mediaId, width, height); - return new FileStreamResult(thumb, "image/jpeg"); - } - - [HttpDelete("{mediaId}")] - public void Delete([FromRoute] Guid mediaId) { - mediaService.Delete(mediaId); - } - - [HttpPost] - public IActionResult Upload() { - if(Request.Form.Files.Count == 0) - throw new ApiModels.ArgumentException("No files"); - - Media media = new(); - - foreach(var formFile in Request.Form.Files) { - // 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()); - } - - if(Request.Form.Files.Count == 1) - return Redirect($"/ViewMedia?m={media.Guid}"); - else - return Redirect($"/Gallery"); - } -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 7769bf4..0000000 --- a/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:f061e5a7532b36fa1d1b684857fe1f504ba92115b9934f154643266613c44c62 AS build -WORKDIR /App/Server - -COPY Server /App/Server -COPY ApiModels /App/ApiModels -RUN dotnet restore -RUN dotnet publish -o out - -FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:ccdca44cd4f256d50187f920dc8ccc2a9ea7a8a4597ac1d51e08fddb2e3b3205 -RUN apt update -RUN apt install -y imagemagick tesseract-ocr tesseract-ocr-eng -RUN apt clean -RUN rm -rf /var/lib/apt/lists/* -WORKDIR /App -COPY --from=build /App/Server/out . -ENTRYPOINT [ "dotnet", "HyperBooru.dll" ] diff --git a/ExceptionMiddleware.cs b/ExceptionMiddleware.cs deleted file mode 100644 index ba4049a..0000000 --- a/ExceptionMiddleware.cs +++ /dev/null @@ -1,64 +0,0 @@ -using HyperBooru.ApiModels; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; - -namespace HyperBooru; - -// Middleware class to intercept API controller exceptions and -// return said exceptions to API clients as serialized JSON objects -public sealed class ExceptionMiddleware { - private RequestDelegate next; - - public ExceptionMiddleware(RequestDelegate next) => - this.next = next; - - public async Task Invoke(HttpContext context) { - try { - await next(context); - } catch(HBException e) { - context.Response.ContentType = "application/json"; - context.Response.StatusCode = - e.GetType().GetCustomAttribute()?.StatusCode ?? - StatusCodes.Status500InternalServerError; - - context.Response.Clear(); - - await context.Response.WriteAsJsonAsync(e); - } catch(Exception) { - context.Response.StatusCode = StatusCodes.Status500InternalServerError; - context.Response.ContentType = "application/json"; - - context.Response.Clear(); - - await context.Response.WriteAsJsonAsync(new ServerException()); - } - } -} - -// This class is needed as the JSON serializer often fails to serialize -// members of the native 'Exception' class -public sealed class ExceptionJsonResolver : DefaultJsonTypeInfoResolver { - public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { - var info = base.GetTypeInfo(type, options); - - if(!typeof(Exception).IsAssignableFrom(type)) - return info; - - string[] excludedProps = [ - "data", - "hResult", - "helpLink", - "innerException", - "source", - "stackTrace", - "targetSite" - ]; - - foreach(var p in info.Properties.Where(p => excludedProps.Contains(p.Name))) - p.ShouldSerialize = (_, _) => false; - - return info; - } -} diff --git a/HBContext.cs b/HBContext.cs deleted file mode 100644 index 766c0a3..0000000 --- a/HBContext.cs +++ /dev/null @@ -1,85 +0,0 @@ -using HyperBooru.ApiModels; -using HyperBooru.Services; -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 Objects { get; set; } - public DbSet Users { get; set; } - public DbSet TagDefinitions { get; set; } - public DbSet Tags { get; set; } - public DbSet Media { get; set; } - public DbSet UploadedFiles { get; set; } - public DbSet OcrData { get; set; } - - private IConfigService config; - - public HBContext(DbContextOptions options, IConfigService config) : base(options) => - this.config = config; - - protected override void OnConfiguring(DbContextOptionsBuilder options) { - options.UseNpgsql(config.DbConnectionString); - - #if DEBUG - options.EnableSensitiveDataLogging(); - #endif - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) { - // Don't use shared tables for inherited types - modelBuilder.Entity().ToTable("Objects"); - modelBuilder.Entity().ToTable("TagDefinitions"); - modelBuilder.Entity().ToTable("Tags"); - modelBuilder.Entity().ToTable("Media"); - modelBuilder.Entity().ToTable("UploadedFiles"); - - // Seed internal tag definitions - // These should NEVER change - modelBuilder.Entity().HasData(new TagDefinition[] { - new() { - ObjectId = (int) HBObjectId.NsfwTag, - Guid = NsfwTag, - Source = TagSource.Internal, - Name = "nsfw" - }, - new() { - ObjectId = (int) HBObjectId.IngestTag, - Guid = IngestTag, - Source = TagSource.Internal, - Name = "ingest" - } - }); - - // Seed initial admin user - modelBuilder.Entity().HasData(new User[] { - new() { - ObjectId = (int) HBObjectId.AdminUser, - Guid = AdminUser, - Username = "admin", - PasswordHash = UserService.HashPassword("admin") - } - }); - - // Some complex relationships cannot be inferred and require - // additional configuration, as seen below. - modelBuilder.Entity() - .HasMany(e => e.ImplicitTags) - .WithMany(); - - modelBuilder.Entity() - .HasOne(m => m.CurrentUploadedFile) - .WithOne() - .HasForeignKey("CurrentUploadedFileId"); - } -} \ No newline at end of file diff --git a/HBObject.cs b/HBObject.cs deleted file mode 100644 index 8001ea3..0000000 --- a/HBObject.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace HyperBooru; - -[Index(nameof(Guid))] -public class HBObject { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int ObjectId { get; set; } - public Guid Guid { get; set; } = Guid.NewGuid(); - public virtual List Tags { get; set; } = new(); -} \ No newline at end of file diff --git a/Media.cs b/Media.cs deleted file mode 100644 index 2ff9e63..0000000 --- a/Media.cs +++ /dev/null @@ -1,92 +0,0 @@ -using HyperBooru.ApiModels; -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace HyperBooru; - -public class Media : HBObject { - public string? ShortDescription { get; set; } - public string? LongDescription { get; set; } - public virtual OcrData? OcrData { get; set; } - public virtual UploadedFile? CurrentUploadedFile { get; set; } - public virtual List UploadedFiles { get; set; } = new(); - - public bool IsIngest => Tags - .Select(t => t.TagDefinitionId) - .Contains((int) HBObjectId.IngestTag); - - public string? DisplayName { - get { - if(ShortDescription is not null) - return ShortDescription; - - return UploadedFiles - .OrderBy(f => f.UploadTime) - .First()?.Filename ?? Guid.ToString().ToUpper(); - } - } - - public static explicit operator ApiModels.Media(Media media) => - new() { - MediaId = media.Guid, - ShortDescription = media.ShortDescription, - LongDescription = media.LongDescription - }; -} - -public class UploadedFile : HBObject { - public string Checksum { get; set; } - public bool ChecksumVerified { get; set; } = false; - public string? Filename { get; set; } - public long Length { get; set; } - public string MimeType { get; set; } - public int? Width { get; set; } - public int? Height { get; set; } - public DateTime UploadTime { get; set; } = DateTime.UtcNow; - public DateTime? LastAccessTime { get; set; } - public DateTime? LastWriteTime { get; set; } - public DateTime? CreateTime { get; set; } - public string? Path { get; set; } - public PathType? PathType { get; set; } - public virtual Media Media { get; set; } - - public static explicit operator ApiModels.UploadedFile(UploadedFile uploadedFile) => - new() { - MediaId = uploadedFile.Media.Guid, - UploadedFileId = uploadedFile.Guid, - Checksum = uploadedFile.Checksum, - ChecksumVerified = uploadedFile.ChecksumVerified, - Filename = uploadedFile.Filename, - Length = uploadedFile.Length, - MimeType = uploadedFile.MimeType, - Width = uploadedFile.Width, - Height = uploadedFile.Height, - UploadTime = uploadedFile.UploadTime, - LastAccessTime = uploadedFile.LastAccessTime, - LastWriteTime = uploadedFile.LastWriteTime, - CreateTime = uploadedFile.CreateTime, - Path = uploadedFile.Path, - PathType = (ApiModels.PathType?) uploadedFile.PathType - }; -} - -public class OcrData { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int OcrDataId { get; set; } - [ForeignKey("ObjectId")] - public int MediaId { get; set; } - public string Text { get; set; } - public string SearchableText { get; set; } - public DateTime Timestamp { get; set; } - public virtual Media Media { get; set; } - - public static explicit operator ApiModels.OcrData(OcrData ocrData) => - new() { - MediaId = ocrData.Media.Guid, - Text = ocrData.Text, - SearchableText = ocrData.SearchableText, - Timestamp = ocrData.Timestamp - }; -} \ No newline at end of file diff --git a/Migrations/20260131125650_InitialMigration.Designer.cs b/Migrations/20260131125650_InitialMigration.Designer.cs deleted file mode 100644 index 2e4a05e..0000000 --- a/Migrations/20260131125650_InitialMigration.Designer.cs +++ /dev/null @@ -1,362 +0,0 @@ -// -using System; -using HyperBooru; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HyperBooru.Migrations -{ - [DbContext(typeof(HBContext))] - [Migration("20260131125650_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.23") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("HyperBooru.HBObject", b => - { - b.Property("ObjectId") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ObjectId")); - - b.Property("Guid") - .HasColumnType("uuid"); - - b.HasKey("ObjectId"); - - b.HasIndex("Guid"); - - b.ToTable("Objects", (string)null); - - b.UseTptMappingStrategy(); - }); - - modelBuilder.Entity("HyperBooru.OcrData", b => - { - b.Property("OcrDataId") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OcrDataId")); - - b.Property("MediaId") - .HasColumnType("integer"); - - b.Property("SearchableText") - .IsRequired() - .HasColumnType("text"); - - b.Property("Text") - .IsRequired() - .HasColumnType("text"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("OcrDataId"); - - b.HasIndex("MediaId") - .IsUnique(); - - b.ToTable("OcrData"); - }); - - modelBuilder.Entity("TagDefinitionTagDefinition", b => - { - b.Property("ImplicitTagsObjectId") - .HasColumnType("integer"); - - b.Property("TagDefinitionObjectId") - .HasColumnType("integer"); - - b.HasKey("ImplicitTagsObjectId", "TagDefinitionObjectId"); - - b.HasIndex("TagDefinitionObjectId"); - - b.ToTable("TagDefinitionTagDefinition"); - }); - - modelBuilder.Entity("HyperBooru.Media", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("CurrentUploadedFileId") - .HasColumnType("integer"); - - b.Property("LongDescription") - .HasColumnType("text"); - - b.Property("ShortDescription") - .HasColumnType("text"); - - b.HasIndex("CurrentUploadedFileId") - .IsUnique(); - - b.ToTable("Media", (string)null); - }); - - modelBuilder.Entity("HyperBooru.Tag", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("CreateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("TagDefinitionId") - .HasColumnType("integer"); - - b.Property("TargetObjectId") - .HasColumnType("integer"); - - b.HasIndex("TagDefinitionId"); - - b.HasIndex("TargetObjectId"); - - b.ToTable("Tags", (string)null); - }); - - modelBuilder.Entity("HyperBooru.TagDefinition", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("Alias") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("Namespace") - .HasColumnType("text"); - - b.Property("Source") - .HasColumnType("integer"); - - b.ToTable("TagDefinitions", (string)null); - - b.HasData( - new - { - ObjectId = -1, - Guid = new Guid("ebdad4f8-455a-4351-8017-1d4854d6fa38"), - Name = "nsfw", - Source = 0 - }, - new - { - ObjectId = -2, - Guid = new Guid("ea212801-5bcc-4c0e-814f-fb9d30db58bc"), - Name = "ingest", - Source = 0 - }); - }); - - modelBuilder.Entity("HyperBooru.UploadedFile", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("Checksum") - .IsRequired() - .HasColumnType("text"); - - b.Property("ChecksumVerified") - .HasColumnType("boolean"); - - b.Property("CreateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Filename") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("integer"); - - b.Property("LastAccessTime") - .HasColumnType("timestamp with time zone"); - - b.Property("LastWriteTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("MediaObjectId") - .HasColumnType("integer"); - - b.Property("MimeType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("PathType") - .HasColumnType("integer"); - - b.Property("UploadTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Width") - .HasColumnType("integer"); - - b.HasIndex("MediaObjectId"); - - b.ToTable("UploadedFiles", (string)null); - }); - - modelBuilder.Entity("HyperBooru.User", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("PasswordHash") - .IsRequired() - .HasColumnType("text"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasIndex("Username"); - - b.ToTable("Users"); - - b.HasData( - new - { - ObjectId = -3, - Guid = new Guid("4fa948f4-7c45-4f81-bb6b-e417491e6c96"), - PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw==", - Username = "admin" - }); - }); - - modelBuilder.Entity("HyperBooru.OcrData", b => - { - b.HasOne("HyperBooru.Media", "Media") - .WithOne("OcrData") - .HasForeignKey("HyperBooru.OcrData", "MediaId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Media"); - }); - - modelBuilder.Entity("TagDefinitionTagDefinition", b => - { - b.HasOne("HyperBooru.TagDefinition", null) - .WithMany() - .HasForeignKey("ImplicitTagsObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.TagDefinition", null) - .WithMany() - .HasForeignKey("TagDefinitionObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("HyperBooru.Media", b => - { - b.HasOne("HyperBooru.UploadedFile", "CurrentUploadedFile") - .WithOne() - .HasForeignKey("HyperBooru.Media", "CurrentUploadedFileId"); - - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.Media", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CurrentUploadedFile"); - }); - - modelBuilder.Entity("HyperBooru.Tag", b => - { - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.Tag", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.TagDefinition", "TagDefinition") - .WithMany() - .HasForeignKey("TagDefinitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.HBObject", "Target") - .WithMany("Tags") - .HasForeignKey("TargetObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TagDefinition"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("HyperBooru.TagDefinition", b => - { - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.TagDefinition", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("HyperBooru.UploadedFile", b => - { - b.HasOne("HyperBooru.Media", "Media") - .WithMany("UploadedFiles") - .HasForeignKey("MediaObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.UploadedFile", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Media"); - }); - - modelBuilder.Entity("HyperBooru.User", b => - { - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.User", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("HyperBooru.HBObject", b => - { - b.Navigation("Tags"); - }); - - modelBuilder.Entity("HyperBooru.Media", b => - { - b.Navigation("OcrData"); - - b.Navigation("UploadedFiles"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Migrations/20260131125650_InitialMigration.cs b/Migrations/20260131125650_InitialMigration.cs deleted file mode 100644 index a1a7d8f..0000000 --- a/Migrations/20260131125650_InitialMigration.cs +++ /dev/null @@ -1,319 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace HyperBooru.Migrations -{ - /// - public partial class InitialMigration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Objects", - columns: table => new - { - ObjectId = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Guid = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Objects", x => x.ObjectId); - }); - - migrationBuilder.CreateTable( - name: "TagDefinitions", - columns: table => new - { - ObjectId = table.Column(type: "integer", nullable: false), - Source = table.Column(type: "integer", nullable: false), - Namespace = table.Column(type: "text", nullable: true), - Name = table.Column(type: "text", nullable: false), - Alias = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TagDefinitions", x => x.ObjectId); - table.ForeignKey( - name: "FK_TagDefinitions_Objects_ObjectId", - column: x => x.ObjectId, - principalTable: "Objects", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - ObjectId = table.Column(type: "integer", nullable: false), - Username = table.Column(type: "text", nullable: false), - PasswordHash = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.ObjectId); - table.ForeignKey( - name: "FK_Users_Objects_ObjectId", - column: x => x.ObjectId, - principalTable: "Objects", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "TagDefinitionTagDefinition", - columns: table => new - { - ImplicitTagsObjectId = table.Column(type: "integer", nullable: false), - TagDefinitionObjectId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TagDefinitionTagDefinition", x => new { x.ImplicitTagsObjectId, x.TagDefinitionObjectId }); - table.ForeignKey( - name: "FK_TagDefinitionTagDefinition_TagDefinitions_ImplicitTagsObjec~", - column: x => x.ImplicitTagsObjectId, - principalTable: "TagDefinitions", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_TagDefinitionTagDefinition_TagDefinitions_TagDefinitionObje~", - column: x => x.TagDefinitionObjectId, - principalTable: "TagDefinitions", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Tags", - columns: table => new - { - ObjectId = table.Column(type: "integer", nullable: false), - TagDefinitionId = table.Column(type: "integer", nullable: false), - CreateTime = table.Column(type: "timestamp with time zone", nullable: false), - TargetObjectId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tags", x => x.ObjectId); - table.ForeignKey( - name: "FK_Tags_Objects_ObjectId", - column: x => x.ObjectId, - principalTable: "Objects", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Tags_Objects_TargetObjectId", - column: x => x.TargetObjectId, - principalTable: "Objects", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Tags_TagDefinitions_TagDefinitionId", - column: x => x.TagDefinitionId, - principalTable: "TagDefinitions", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Media", - columns: table => new - { - ObjectId = table.Column(type: "integer", nullable: false), - ShortDescription = table.Column(type: "text", nullable: true), - LongDescription = table.Column(type: "text", nullable: true), - CurrentUploadedFileId = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Media", x => x.ObjectId); - table.ForeignKey( - name: "FK_Media_Objects_ObjectId", - column: x => x.ObjectId, - principalTable: "Objects", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "OcrData", - columns: table => new - { - OcrDataId = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - MediaId = table.Column(type: "integer", nullable: false), - Text = table.Column(type: "text", nullable: false), - SearchableText = table.Column(type: "text", nullable: false), - Timestamp = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_OcrData", x => x.OcrDataId); - table.ForeignKey( - name: "FK_OcrData_Media_MediaId", - column: x => x.MediaId, - principalTable: "Media", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "UploadedFiles", - columns: table => new - { - ObjectId = table.Column(type: "integer", nullable: false), - Checksum = table.Column(type: "text", nullable: false), - ChecksumVerified = table.Column(type: "boolean", nullable: false), - Filename = table.Column(type: "text", nullable: true), - Length = table.Column(type: "bigint", nullable: false), - MimeType = table.Column(type: "text", nullable: false), - Width = table.Column(type: "integer", nullable: true), - Height = table.Column(type: "integer", nullable: true), - UploadTime = table.Column(type: "timestamp with time zone", nullable: false), - LastAccessTime = table.Column(type: "timestamp with time zone", nullable: true), - LastWriteTime = table.Column(type: "timestamp with time zone", nullable: true), - CreateTime = table.Column(type: "timestamp with time zone", nullable: true), - Path = table.Column(type: "text", nullable: true), - PathType = table.Column(type: "integer", nullable: true), - MediaObjectId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UploadedFiles", x => x.ObjectId); - table.ForeignKey( - name: "FK_UploadedFiles_Media_MediaObjectId", - column: x => x.MediaObjectId, - principalTable: "Media", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_UploadedFiles_Objects_ObjectId", - column: x => x.ObjectId, - principalTable: "Objects", - principalColumn: "ObjectId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - table: "Objects", - columns: new[] { "ObjectId", "Guid" }, - values: new object[,] - { - { -3, new Guid("4fa948f4-7c45-4f81-bb6b-e417491e6c96") }, - { -2, new Guid("ea212801-5bcc-4c0e-814f-fb9d30db58bc") }, - { -1, new Guid("ebdad4f8-455a-4351-8017-1d4854d6fa38") } - }); - - migrationBuilder.InsertData( - table: "TagDefinitions", - columns: new[] { "ObjectId", "Alias", "Name", "Namespace", "Source" }, - values: new object[,] - { - { -2, null, "ingest", null, 0 }, - { -1, null, "nsfw", null, 0 } - }); - - migrationBuilder.InsertData( - table: "Users", - columns: new[] { "ObjectId", "PasswordHash", "Username" }, - values: new object[] { -3, "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw==", "admin" }); - - migrationBuilder.CreateIndex( - name: "IX_Media_CurrentUploadedFileId", - table: "Media", - column: "CurrentUploadedFileId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Objects_Guid", - table: "Objects", - column: "Guid"); - - migrationBuilder.CreateIndex( - name: "IX_OcrData_MediaId", - table: "OcrData", - column: "MediaId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_TagDefinitionTagDefinition_TagDefinitionObjectId", - table: "TagDefinitionTagDefinition", - column: "TagDefinitionObjectId"); - - migrationBuilder.CreateIndex( - name: "IX_Tags_TagDefinitionId", - table: "Tags", - column: "TagDefinitionId"); - - migrationBuilder.CreateIndex( - name: "IX_Tags_TargetObjectId", - table: "Tags", - column: "TargetObjectId"); - - migrationBuilder.CreateIndex( - name: "IX_UploadedFiles_MediaObjectId", - table: "UploadedFiles", - column: "MediaObjectId"); - - migrationBuilder.CreateIndex( - name: "IX_Users_Username", - table: "Users", - column: "Username"); - - migrationBuilder.AddForeignKey( - name: "FK_Media_UploadedFiles_CurrentUploadedFileId", - table: "Media", - column: "CurrentUploadedFileId", - principalTable: "UploadedFiles", - principalColumn: "ObjectId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Media_Objects_ObjectId", - table: "Media"); - - migrationBuilder.DropForeignKey( - name: "FK_UploadedFiles_Objects_ObjectId", - table: "UploadedFiles"); - - migrationBuilder.DropForeignKey( - name: "FK_Media_UploadedFiles_CurrentUploadedFileId", - table: "Media"); - - migrationBuilder.DropTable( - name: "OcrData"); - - migrationBuilder.DropTable( - name: "TagDefinitionTagDefinition"); - - migrationBuilder.DropTable( - name: "Tags"); - - migrationBuilder.DropTable( - name: "Users"); - - migrationBuilder.DropTable( - name: "TagDefinitions"); - - migrationBuilder.DropTable( - name: "Objects"); - - migrationBuilder.DropTable( - name: "UploadedFiles"); - - migrationBuilder.DropTable( - name: "Media"); - } - } -} diff --git a/Migrations/HBContextModelSnapshot.cs b/Migrations/HBContextModelSnapshot.cs deleted file mode 100644 index 422037f..0000000 --- a/Migrations/HBContextModelSnapshot.cs +++ /dev/null @@ -1,359 +0,0 @@ -// -using System; -using HyperBooru; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HyperBooru.Migrations -{ - [DbContext(typeof(HBContext))] - partial class HBContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.23") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("HyperBooru.HBObject", b => - { - b.Property("ObjectId") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ObjectId")); - - b.Property("Guid") - .HasColumnType("uuid"); - - b.HasKey("ObjectId"); - - b.HasIndex("Guid"); - - b.ToTable("Objects", (string)null); - - b.UseTptMappingStrategy(); - }); - - modelBuilder.Entity("HyperBooru.OcrData", b => - { - b.Property("OcrDataId") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OcrDataId")); - - b.Property("MediaId") - .HasColumnType("integer"); - - b.Property("SearchableText") - .IsRequired() - .HasColumnType("text"); - - b.Property("Text") - .IsRequired() - .HasColumnType("text"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("OcrDataId"); - - b.HasIndex("MediaId") - .IsUnique(); - - b.ToTable("OcrData"); - }); - - modelBuilder.Entity("TagDefinitionTagDefinition", b => - { - b.Property("ImplicitTagsObjectId") - .HasColumnType("integer"); - - b.Property("TagDefinitionObjectId") - .HasColumnType("integer"); - - b.HasKey("ImplicitTagsObjectId", "TagDefinitionObjectId"); - - b.HasIndex("TagDefinitionObjectId"); - - b.ToTable("TagDefinitionTagDefinition"); - }); - - modelBuilder.Entity("HyperBooru.Media", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("CurrentUploadedFileId") - .HasColumnType("integer"); - - b.Property("LongDescription") - .HasColumnType("text"); - - b.Property("ShortDescription") - .HasColumnType("text"); - - b.HasIndex("CurrentUploadedFileId") - .IsUnique(); - - b.ToTable("Media", (string)null); - }); - - modelBuilder.Entity("HyperBooru.Tag", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("CreateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("TagDefinitionId") - .HasColumnType("integer"); - - b.Property("TargetObjectId") - .HasColumnType("integer"); - - b.HasIndex("TagDefinitionId"); - - b.HasIndex("TargetObjectId"); - - b.ToTable("Tags", (string)null); - }); - - modelBuilder.Entity("HyperBooru.TagDefinition", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("Alias") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("Namespace") - .HasColumnType("text"); - - b.Property("Source") - .HasColumnType("integer"); - - b.ToTable("TagDefinitions", (string)null); - - b.HasData( - new - { - ObjectId = -1, - Guid = new Guid("ebdad4f8-455a-4351-8017-1d4854d6fa38"), - Name = "nsfw", - Source = 0 - }, - new - { - ObjectId = -2, - Guid = new Guid("ea212801-5bcc-4c0e-814f-fb9d30db58bc"), - Name = "ingest", - Source = 0 - }); - }); - - modelBuilder.Entity("HyperBooru.UploadedFile", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("Checksum") - .IsRequired() - .HasColumnType("text"); - - b.Property("ChecksumVerified") - .HasColumnType("boolean"); - - b.Property("CreateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Filename") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("integer"); - - b.Property("LastAccessTime") - .HasColumnType("timestamp with time zone"); - - b.Property("LastWriteTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("MediaObjectId") - .HasColumnType("integer"); - - b.Property("MimeType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("PathType") - .HasColumnType("integer"); - - b.Property("UploadTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Width") - .HasColumnType("integer"); - - b.HasIndex("MediaObjectId"); - - b.ToTable("UploadedFiles", (string)null); - }); - - modelBuilder.Entity("HyperBooru.User", b => - { - b.HasBaseType("HyperBooru.HBObject"); - - b.Property("PasswordHash") - .IsRequired() - .HasColumnType("text"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasIndex("Username"); - - b.ToTable("Users"); - - b.HasData( - new - { - ObjectId = -3, - Guid = new Guid("4fa948f4-7c45-4f81-bb6b-e417491e6c96"), - PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw==", - Username = "admin" - }); - }); - - modelBuilder.Entity("HyperBooru.OcrData", b => - { - b.HasOne("HyperBooru.Media", "Media") - .WithOne("OcrData") - .HasForeignKey("HyperBooru.OcrData", "MediaId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Media"); - }); - - modelBuilder.Entity("TagDefinitionTagDefinition", b => - { - b.HasOne("HyperBooru.TagDefinition", null) - .WithMany() - .HasForeignKey("ImplicitTagsObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.TagDefinition", null) - .WithMany() - .HasForeignKey("TagDefinitionObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("HyperBooru.Media", b => - { - b.HasOne("HyperBooru.UploadedFile", "CurrentUploadedFile") - .WithOne() - .HasForeignKey("HyperBooru.Media", "CurrentUploadedFileId"); - - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.Media", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CurrentUploadedFile"); - }); - - modelBuilder.Entity("HyperBooru.Tag", b => - { - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.Tag", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.TagDefinition", "TagDefinition") - .WithMany() - .HasForeignKey("TagDefinitionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.HBObject", "Target") - .WithMany("Tags") - .HasForeignKey("TargetObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TagDefinition"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("HyperBooru.TagDefinition", b => - { - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.TagDefinition", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("HyperBooru.UploadedFile", b => - { - b.HasOne("HyperBooru.Media", "Media") - .WithMany("UploadedFiles") - .HasForeignKey("MediaObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.UploadedFile", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Media"); - }); - - modelBuilder.Entity("HyperBooru.User", b => - { - b.HasOne("HyperBooru.HBObject", null) - .WithOne() - .HasForeignKey("HyperBooru.User", "ObjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("HyperBooru.HBObject", b => - { - b.Navigation("Tags"); - }); - - modelBuilder.Entity("HyperBooru.Media", b => - { - b.Navigation("OcrData"); - - b.Navigation("UploadedFiles"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Pages/_Host.cshtml b/Pages/_Host.cshtml deleted file mode 100644 index 28ff24c..0000000 --- a/Pages/_Host.cshtml +++ /dev/null @@ -1,37 +0,0 @@ -@page "/" -@using Microsoft.AspNetCore.Components.Web -@namespace HyperBooru.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers - - - - - - - - - - - - - - - - - - - -
- - An error has occurred. This application may no longer respond until reloaded. - - - An unhandled exception has occurred. See browser dev tools for details. - - Reload - 🗙 -
- - - - diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json deleted file mode 100644 index 9f4966c..0000000 --- a/Properties/launchSettings.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "profiles": { - "WSL": { - "commandName": "WSL2", - "launchBrowser": true, - "launchUrl": "https://localhost:7132", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "https://localhost:7132;http://localhost:5186" - }, - "distributionName": "" - }, - "HyperBooru": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7132;http://localhost:5186" - } - }, - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:1922", - "sslPort": 44354 - } - } -} \ No newline at end of file diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs deleted file mode 100644 index ac1f155..0000000 --- a/Services/ConfigService.cs +++ /dev/null @@ -1,72 +0,0 @@ -using HyperBooru.ApiModels; - -namespace HyperBooru.Services; - -public interface IConfigService { - public string DataPath { get; } - public string KeyPath { get; } - public string DbConnectionString { get; } - public string MediaBasePath { get; } - public string ThumbnailBasePath { get; } - public string ConvertedMediaBasePath { get; } - public bool EnableOcr { get; } -} - -public class ConfigService : IConfigService { - private IConfiguration config; - - private const string AppName = "HyperBooru"; - - public string DataPath { - get { - #if DEBUG - return "Data"; - #else - string? path = config["DataPath"]; - if(path is not null) - return path; - - switch(Environment.OSVersion.Platform) { - case PlatformID.Win32NT: - return Path.Join( - Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), - AppName); - case PlatformID.Unix: - return $"/var/lib/{AppName.ToLower()}"; - default: - throw new NotImplementedException( - $"Unknown Operating System: {Environment.OSVersion.Platform}"); - } - #endif - } - } - - public string KeyPath => - Path.Join(DataPath, "keys"); - - public string DbConnectionString => - config.GetConnectionString("DefaultConnection") ?? - throw new HBException("Unable to get default connection string"); - - public string MediaBasePath => - Path.Join(DataPath, "media"); - - public string ThumbnailBasePath => - Path.Join(DataPath, "thumb"); - - public string ConvertedMediaBasePath => - Path.Join(DataPath, "converted"); - - public bool EnableOcr => - bool.TryParse(config["DisableOcr"], out bool x) ? !x : true; - - public ConfigService(IConfiguration config) { - this.config = config; - InitDirectoryStructure(); - } - - private void InitDirectoryStructure() { - Directory.CreateDirectory(DataPath); - Directory.CreateDirectory(MediaBasePath); - } -} \ No newline at end of file diff --git a/Services/FeedService.cs b/Services/FeedService.cs deleted file mode 100644 index 3744e73..0000000 --- a/Services/FeedService.cs +++ /dev/null @@ -1,212 +0,0 @@ -using HyperBooru.ApiModels; -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru.Services; - -public interface IFeedService { - public Media[] LoadChunk( - bool selectIngest, - bool includeNsfw, - Media? key = null, - int count = 50, - SortOrder sortOrder = SortOrder.ObjectId); - - public Media[] LoadChunk( - bool selectIngest, - bool includeNsfw, - string query, - Media? key = null, - int count = 50, - SortOrder sortOrder = SortOrder.ObjectId); - - public Media[] LoadChunk( - bool selectIngest, - bool includeNsfw, - Guid tagId, - Media? key = null, - int count = 50, - SortOrder sortOrder = SortOrder.ObjectId); - - public Media[] LoadChunk(FeedRequest feedRequest); -} - -public class FeedService : IFeedService { - private IDbContextFactory dbFactory; - - public FeedService(IDbContextFactory dbFactory) => - this.dbFactory = dbFactory; - - public Media[] LoadChunk( - bool selectIngest, - bool includeNsfw, - Media? continuationToken, - int count, - SortOrder sortOrder) => LoadChunkInternal( - selectIngest, includeNsfw, null, null, continuationToken?.Guid, count, sortOrder); - - public Media[] LoadChunk( - bool selectIngest, - bool includeNsfw, - string query, - Media? continuationToken, - int count, - SortOrder sortOrder) => LoadChunkInternal( - selectIngest, includeNsfw, query, null, continuationToken?.Guid, count, sortOrder); - - public Media[] LoadChunk( - bool selectIngest, - bool includeNsfw, - Guid tagId, - Media? continuationToken, - int count, - SortOrder sortOrder) => LoadChunkInternal( - selectIngest, includeNsfw, null, tagId, continuationToken?.Guid, count, sortOrder); - - public Media[] LoadChunk(FeedRequest feedRequest) { - switch(feedRequest) { - case FeedSearchRequest searchRequest: - return LoadChunkInternal( - selectIngest: searchRequest.SelectIngest, - includeNsfw: searchRequest.IncludeNsfw, - query: searchRequest.Query, - tagId: null, - continuationToken: searchRequest.ContinuationToken, - count: searchRequest.Count, - sortOrder: searchRequest.SortOrder); - case FeedTagRequest tagRequest: - return LoadChunkInternal( - selectIngest: tagRequest.SelectIngest, - includeNsfw: tagRequest.IncludeNsfw, - query: null, - tagId: tagRequest.TagId, - continuationToken: tagRequest.ContinuationToken, - count: tagRequest.Count, - sortOrder: tagRequest.SortOrder); - default: - return LoadChunkInternal( - selectIngest: feedRequest.SelectIngest, - includeNsfw: feedRequest.IncludeNsfw, - query: null, - tagId: null, - continuationToken: feedRequest.ContinuationToken, - count: feedRequest.Count, - sortOrder: feedRequest.SortOrder); - } - } - - private Media[] LoadChunkInternal( - bool selectIngest, - bool includeNsfw, - string? query, - Guid? tagId, - Guid? continuationToken, - int count, - SortOrder sortOrder) { - - if(selectIngest && !includeNsfw) - return Array.Empty(); - - using var db = dbFactory.CreateDbContext(); - - IQueryable media = db.Media - .AsSingleQuery() - .AsNoTracking() - .Include(m => m.Tags) - .Include(m => m.CurrentUploadedFile); - - if(!includeNsfw) - media = media - .Where(m => !TagsThatImply(db, HBContext.NsfwTag) - .Intersect(m.Tags.Select(t => t.TagDefinitionId)) - .Any()); - - if(selectIngest) { - media = media - .Where(m => m.Tags - .Select(t => t.TagDefinitionId) - .Contains((int) HBObjectId.IngestTag)); - } else { - media = media - .Where(m => !m.Tags - .Select(t => t.TagDefinitionId) - .Contains((int) HBObjectId.IngestTag)); - } - - if(query is not null) { - media = Search(media, query); - } else if(tagId is not null) { - media = media - .Where(m => TagsThatImply(db, (Guid) tagId) - .Intersect(m.Tags.Select(t => t.TagDefinitionId)) - .Any()); - } - - if(continuationToken is not null) - media = media - .Where(m => m.ObjectId > db.Media.First(m => m.Guid == continuationToken).ObjectId); - - switch(sortOrder) { - case SortOrder.ObjectId: - media = media.OrderBy(m => m.ObjectId); - break; - case SortOrder.LastWriteTime: - media = media.OrderBy(m => m.CurrentUploadedFile!.LastWriteTime); - break; - case SortOrder.Random: - media = media.OrderBy(m => EF.Functions.Random()); - break; - } - - return media - .Take(count) - .ToArray(); - } - - private static IQueryable Search(IQueryable media, string query) { - // TODO: search implicit tags as well - - query = query.ToLower().Trim(); - - return media - .Where(m => - (m.ShortDescription != null && m.ShortDescription.ToLower().Contains(query)) || - (m.LongDescription != null && m.LongDescription.ToLower().Contains(query)) || - (m.UploadedFiles.Any(uf => uf.Filename != null && uf.Filename.ToLower().Contains(query))) || - (m.OcrData != null && m.OcrData.SearchableText.ToLower().Contains(query)) || - (m.Tags.Any(t => t.TagDefinition.Name.ToLower().Contains(query)))); - } - - private static IQueryable TagsThatImply(HBContext db, Guid tagId) => - db.Database.SqlQueryRaw(""" - WITH RECURSIVE basetag AS ( - SELECT "ObjectId" FROM "Objects" WHERE "Guid" = {0} - ), - impliedtags AS ( - SELECT - "TagDefinitionObjectId" - FROM - "TagDefinitionTagDefinition" - INNER JOIN - basetag - ON - "ImplicitTagsObjectId" = basetag."ObjectId" - UNION - SELECT - "TagDefinitionTagDefinition"."TagDefinitionObjectId" - FROM - "TagDefinitionTagDefinition" - INNER JOIN - impliedtags - ON - impliedtags."TagDefinitionObjectId" = "TagDefinitionTagDefinition"."ImplicitTagsObjectId" - ) - SELECT DISTINCT - "TagDefinitionObjectId" AS "Value" - FROM impliedtags - UNION - SELECT - "ObjectId" AS "Value" - FROM - basetag - """, tagId); -} diff --git a/Services/MediaService.cs b/Services/MediaService.cs deleted file mode 100644 index 2d1533c..0000000 --- a/Services/MediaService.cs +++ /dev/null @@ -1,400 +0,0 @@ -using HyperBooru.ApiModels; -using ImageMagick; -using Microsoft.EntityFrameworkCore; -using MimeDetective; -using MimeDetective.Definitions; -using System.Security.Cryptography; -using System.Text.RegularExpressions; - -namespace HyperBooru.Services; - -public interface IMediaService { - public void SetDescription( - Media media, - string? shortDescription, - string? longDescription); - - public void SetIngest(Media media, bool ingest); - - public Media Create( - Stream fileData, - string fileName, - string? checksum = null, - DateTime? lastAccessTime = null, - DateTime? lastWriteTime = null, - DateTime? createTime = null, - string? path = null, - PathType? pathType = null, - Guid[]? tagIds = null); - - public void Delete(Guid media); - public void Delete(Media media); - public void DeleteThumbnails(Guid media); - public void DeleteThumbnails(Media media); - public Stream GetThumbnail(Guid media, int? width, int? height); - public Stream GetThumbnail(Media media, int? width, int? height); - public Stream GetConverted(Guid mediaId, string mimeType = "image/png"); - public Stream GetConverted(Media media, string mimeType = "image/png"); - public string GetPath(Guid media); - public string GetPath(Media media); - -} - -public class MediaService : IMediaService { - private readonly Dictionary FormatMap = new() { - ["image/jpeg"] = MagickFormat.Jpeg, - ["image/jpg"] = MagickFormat.Jpg, - ["image/png"] = MagickFormat.Png, - ["image/webp"] = MagickFormat.WebP - }; - - private IDbContextFactory dbFactory; - private IConfigService config; - - private IContentInspector inspector; - - public MediaService(IDbContextFactory dbFactory, - IConfigService config) { - - this.dbFactory = dbFactory; - this.config = config; - - ContentInspectorBuilder inspectorBuilder = new() { - Definitions = - DefaultDefinitions.FileTypes.Images.All() - .Union(DefaultDefinitions.FileTypes.Video.All()) - .ToList() - }; - - inspector = inspectorBuilder.Build(); - } - - public void SetIngest(Media media, bool ingest) { - using var db = dbFactory.CreateDbContext(); - media = db.Media - .Include(m => m.Tags) - .ThenInclude(t => t.TagDefinition) - .First(m => m.Guid == media.Guid); - var ingestTag = db.TagDefinitions - .First(td => td.Guid == HBContext.IngestTag); - - if(ingest) { - if(!media.Tags.Select(t => t.TagDefinition.Guid).Contains(HBContext.IngestTag)) - media.Tags.Add(new(ingestTag)); - } else { - media.Tags.RemoveAll(t => t.TagDefinition.Guid == HBContext.IngestTag); - } - - db.SaveChanges(); - } - - public void SetDescription( - Media media, - string? shortDescription, - string? longDescription) { - - using var db = dbFactory.CreateDbContext(); - var m = db.Media.First(m => m.Guid == media.Guid); - - shortDescription = shortDescription?.Trim(); - longDescription = longDescription?.Trim(); - - if(string.IsNullOrEmpty(shortDescription)) - shortDescription = null; - if(string.IsNullOrEmpty(longDescription)) - longDescription = null; - - m.ShortDescription = shortDescription; - m.LongDescription = longDescription; - - db.SaveChanges(); - } - - public Media Create( - Stream fileData, - string fileName, - string? checksum = null, - DateTime? lastAccessTime = null, - DateTime? lastWriteTime = null, - DateTime? createTime = null, - string? path = null, - PathType? pathType = null, - Guid[]? tagIds = null) { - - using var db = dbFactory.CreateDbContext(); - using var transaction = db.Database.BeginTransaction(); - - if(fileData.Length == 0) - throw new MediaCreateException("File is empty"); - - // Calculate the checksum using the in-memory file contents - var hash = BitConverter - .ToString(MD5.Create().ComputeHash(fileData)) - .Replace("-", "") - .ToLower(); - - if(checksum is not null && hash != checksum.ToLower()) - throw new MediaCreateException("Checksum does not match"); - - // Determine the MIME type - fileData.Seek(0, SeekOrigin.Begin); - var defs = inspector.Inspect(fileData); - var mime = defs.ByMimeType().FirstOrDefault()?.MimeType; - if(mime is null) - throw new MediaCreateException("Unsupported file type"); - - // Read the image with ImageMagick to determine the width and height - fileData.Seek(0, SeekOrigin.Begin); - using var magickImage = new MagickImage(fileData); - - var media = db.Media - .Include(m => m.UploadedFiles) - .Include(m => m.Tags) - .FirstOrDefault(m => m.UploadedFiles.Any(uf => uf.Checksum == hash)); - - var fileRecord = new UploadedFile() { - Filename = fileName, - Length = fileData.Length, - Checksum = hash, - ChecksumVerified = checksum is not null, - MimeType = mime, - Width = (int) magickImage.Width, - Height = (int) magickImage.Height, - UploadTime = DateTime.UtcNow, - LastAccessTime = lastAccessTime, - LastWriteTime = lastWriteTime, - CreateTime = createTime, - Path = pathType is null ? null : path, - PathType = pathType - }; - - var tags = Array.Empty(); - if(tagIds is not null) { - tagIds = tagIds.Distinct().ToArray(); - - tags = db.TagDefinitions - .Where(td => tagIds.Contains(td.Guid)) - .ToArray(); - - if(tags.Count() < tagIds.Count()) { - var badIds = tagIds - .Where(x => !tags.Select(td => td.Guid).Contains(x)) - .Order(); - - throw new MediaCreateException( - $"Non-existent tags specified: {string.Join(", ", badIds)}"); - } - } - - if(media is null) { - var ingestTagDef = db.TagDefinitions - .First(td => td.Guid == HBContext.IngestTag); - - media = new() { - UploadedFiles = new() { - fileRecord - }, - Tags = tags is null ? [ new() { TagDefinition = ingestTagDef } ] : tags - .Select(td => new Tag() { TagDefinition = td }) - .ToList() - }; - - using var newFile = File.Create(GetPath(media)); - - fileData.Seek(0, SeekOrigin.Begin); - fileData.CopyTo(newFile); - newFile.Flush(); - - db.Media.Add(media); - db.SaveChanges(); - media.CurrentUploadedFile = fileRecord; - db.SaveChanges(); - } else { - var fileHashes = media.UploadedFiles - .Select(uf => GetUploadedFileHash(uf)); - // Only add the uploaded file record if it contains new information - if(!fileHashes.Contains(GetUploadedFileHash(fileRecord))) - media.UploadedFiles.Add(fileRecord); - // Add new tags if needed - var missingTags = tags - .Where(td => !media.Tags.Select(t => t.TagDefinition.Guid).Contains(td.Guid)); - media.Tags.AddRange(missingTags.Select(td => new Tag() { TagDefinition = td })); - db.Update(media); - db.SaveChanges(); - } - - transaction.Commit(); - - return media; - } - - public void Delete(Guid media) { - using var db = dbFactory.CreateDbContext(); - var m = db.Media.First(m => m.Guid == media); - - var path = Path.Join( - config.MediaBasePath, - m.Guid.ToString().Substring(0, 2), - m.Guid.ToString().Substring(2, 2), - m.Guid.ToString()); - - try { - var fileInfo = new FileInfo(path); - fileInfo.Delete(); - fileInfo.Directory?.Delete(); - fileInfo.Directory?.Parent?.Delete(); - } catch(IOException) {} - - try { - DeleteThumbnails(media); - } catch {} - - db.Media.Remove(m); - db.SaveChanges(); - } - - public void Delete(Media media) => - Delete(media.Guid); - - public void DeleteThumbnails(Guid media) { - var dir = new DirectoryInfo(Path.Join( - config.ThumbnailBasePath, - media.ToString().Substring(0, 2), - media.ToString().Substring(2, 2))); - - var pattern = new Regex($"^{media}-[0-9]+-[0-9]+$"); - var toDelete = dir.GetFiles() - .Where(f => pattern.IsMatch(f.Name)) - .ToList(); - - List exceptions = new(); - - foreach(var file in toDelete) { - try { - file.Delete(); - } catch(Exception e) { - exceptions.Add(e); - } - } - - try { - dir.Delete(); - dir.Parent?.Delete(); - } catch(Exception e) { - exceptions.Add(e); - } - - // TODO: wrap the AggregateException in a ThumbnailException - if(exceptions.Count() > 1) - throw new AggregateException(exceptions); - } - - public void DeleteThumbnails(Media media) => - DeleteThumbnails(media.Guid); - - 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); - - var thumbPath = GetThumbnailPath(mediaId, width, height); - - if(File.Exists(thumbPath)) - return System.IO.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) { - width = (int) image.Width; - height = (int) image.Height; - } - - image.Thumbnail((uint) (width ?? -1), (uint) (height ?? -1)); - image.Write(thumbPath, MagickFormat.Jpeg); - - return System.IO.File.OpenRead(thumbPath); - } - - public Stream GetConverted(Guid mediaId, string mimeType) { - if(!FormatMap.TryGetValue(mimeType, out var format)) - throw new MediaException($"Cannot convert to unknown format ({mimeType})", mediaId); - - var convertedPath = GetConvertedPath(mediaId, mimeType); - - if(File.Exists(convertedPath)) - return System.IO.File.OpenRead(convertedPath); - - if(!File.Exists(GetPath(mediaId))) - throw new ObjectNotFoundException([ mediaId ]); - - using var image = new MagickImage(GetPath(mediaId)); - image.Write(convertedPath, format); - - return System.IO.File.OpenRead(convertedPath); - } - - public Stream GetThumbnail(Media media, int? width, int? height) => - GetThumbnail(media.Guid, width, height); - - public Stream GetConverted(Media media, string mimeType) => - GetConverted(media.Guid, mimeType); - - public string GetPath(Guid mediaId) { - var fileInfo = new FileInfo( - Path.Join( - config.MediaBasePath, - mediaId.ToString().Substring(0, 2), - mediaId.ToString().Substring(2, 2), - mediaId.ToString())); - - Directory.CreateDirectory(fileInfo.Directory!.FullName); - return fileInfo.FullName; - } - - public string GetThumbnailPath(Guid mediaId, int? width, int? height) { - if(width is null && height is null) - throw new ThumbnailException( - "Both width and height cannot be null!", - mediaId); - - var fileInfo = new FileInfo(Path.Join( - config.ThumbnailBasePath, - mediaId.ToString().Substring(0, 2), - mediaId.ToString().Substring(2, 2), - $"{mediaId.ToString()}-{(width ?? 0)}-{(height ?? 0)}")); - - Directory.CreateDirectory(fileInfo.Directory!.FullName); - return fileInfo.FullName; - } - - public string GetConvertedPath(Guid mediaId, string mimeType) { - var fileInfo = new FileInfo(Path.Join( - config.ConvertedMediaBasePath, - mediaId.ToString().Substring(0, 2), - mediaId.ToString().Substring(2, 2), - $"{mediaId.ToString()}-{mimeType.Split('/')[1]}")); - - Directory.CreateDirectory(fileInfo.Directory!.FullName); - return fileInfo.FullName; - } - - public string GetPath(Media media) => - GetPath(media.Guid); - - public string GetThumbnailPath(Media media, int? width, int? height) => - GetThumbnailPath(media.Guid, width, height); - - public string GetConvertedPath(Media media, string mimeType) => - GetConvertedPath(media.Guid, mimeType); - - private int GetUploadedFileHash(UploadedFile uf) => ( - uf.CreateTime, - uf.LastWriteTime, - uf.Filename, - uf.Length, - uf.Checksum).GetHashCode(); -} diff --git a/Services/OcrService.cs b/Services/OcrService.cs deleted file mode 100644 index d43db2e..0000000 --- a/Services/OcrService.cs +++ /dev/null @@ -1,128 +0,0 @@ -using HyperBooru.Util; -using Microsoft.EntityFrameworkCore; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using Tesseract; - -namespace HyperBooru.Services; - -public class OcrService : IHostedService { - private readonly string[] InvalidMimeTypes = [ "image/heic", "image/webp" ]; - - private readonly TimeSpan ProcessInterval = TimeSpan.FromMinutes(30); - private readonly TimeSpan StartupDelay = TimeSpan.FromSeconds(30); - - private readonly Regex SpaceRegex = new(@"[^0-9a-z]+", RegexOptions.Compiled); - - private Task? task; - private CancellationTokenSource cts = new(); - - private Timer timer; - - private IConfigService configService; - private IServiceScopeFactory scopeFactory; - private ILogger logger; - private IDbContextFactory dbFactory; - - public OcrService( - IConfigService configService, - IServiceScopeFactory scopeFactory, - ILogger logger, - IDbContextFactory dbFactory) { - - this.configService = configService; - this.scopeFactory = scopeFactory; - this.logger = logger; - this.dbFactory = dbFactory; - - timer = new((object? state) => { - if(task is not null && !task.IsCompleted) - return; - cts = new(); - task = ProcessAllAsync(cts.Token); - }); - } - - public Task StartAsync(CancellationToken ct) { - if(configService.EnableOcr) { - logger.LogInformation("Service starting..."); - timer.Change(StartupDelay, ProcessInterval); - } - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken ct) { - logger.LogInformation("Service stopping..."); - timer.Change(Timeout.Infinite, Timeout.Infinite); - cts.Cancel(); - return Task.CompletedTask; - } - - async Task ProcessAllAsync(CancellationToken ct) { - using var scope = scopeFactory.CreateScope(); - var mediaService = scope.ServiceProvider - .GetRequiredService(); - - using var db = dbFactory.CreateDbContext(); - Guid[] guids = db.Media - .AsNoTracking() - .Include(m => m.CurrentUploadedFile) - .Include(m => m.OcrData) - .Where(m => m.OcrData == null) - .Where(m => m.CurrentUploadedFile!.MimeType.Contains("image/")) - .Where(m => !InvalidMimeTypes.Contains(m.CurrentUploadedFile!.MimeType)) - .Select(m => m.Guid) - .ToArray(); - db.Dispose(); - - logger.LogInformation($"Performing OCR pass on {guids.Count()} media items"); - - var factory = new TaskFactory(new LimitedConcurrencyTaskScheduler()); - var tasks = new List(); - - var stopwatch = Stopwatch.StartNew(); - - foreach(var guid in guids) - tasks.Add(factory.StartNew(() => Process(guid, mediaService), ct)); - - await Task.WhenAll(tasks); - stopwatch.Stop(); - - var time = stopwatch.Elapsed.ToStringHumanReadable(); - logger.LogInformation( - $"Performed OCR pass on {guids.Count()} media items in {time}"); - } - - private void Process(Guid media, IMediaService mediaService) { - logger.LogDebug($"Performing OCR on media item {media}"); - - using var db = dbFactory.CreateDbContext(); - var m = db.Media - .Include(m => m.OcrData) - .First(m => m.Guid == media); - - OcrData o = m.OcrData ?? new(); - - using var engine = new TesseractEngine("tessdata", "eng", EngineMode.Default); - using var image = Pix.LoadFromFile(mediaService.GetPath(m)); - engine.SetVariable("debug_file", NullFile); - - o.Timestamp = DateTime.UtcNow; - o.Text = engine.Process(image).GetText().Trim(); - o.SearchableText = SpaceRegex.Replace(o.Text.ToLower(), " ").Trim(); - - m.OcrData = o; - db.SaveChanges(); - } - - private string NullFile { - get { - if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return "NUL"; - else - return "/dev/null"; - } - } -} diff --git a/Services/TagService.cs b/Services/TagService.cs deleted file mode 100644 index f7b91dc..0000000 --- a/Services/TagService.cs +++ /dev/null @@ -1,305 +0,0 @@ -using HyperBooru.ApiModels; -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru.Services; - -public interface ITagService { - public void AddTag(Guid obj, Guid tagDef); - public void AddTag(HBObject obj, TagDefinition tagDef); - public void RemoveTag(Guid obj, Guid tagDef); - public void RemoveTag(HBObject obj, TagDefinition tagDef); - public void SetImplicitTags(TagDefinition tagDef, TagDefinition[] implicitTagDefs); - public void SetImplicitTags(Guid tagDef, Guid[] implicitTagDefs); - public void AddImplicitTag(Guid tagDef, Guid implicitTagDef); - public void AddImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef); - public void RemoveImplicitTag(Guid tagDef, Guid implicitTagDef); - public void RemoveImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef); - public void CreateTagDefinition(string name, string? @namespace = null, string? alias = null); - public void DeleteTagDefinition(Guid tagDef); - public void DeleteTagDefinition(TagDefinition tagDef); - public void UpdateTagDefinition(Guid tagDef, string name, string? @namespace = null, string? alias = null); - public void UpdateTagDefinition(TagDefinition tagDef, string name, string? @namespace = null, string? alias = null); - public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(Guid obj); - public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(HBObject obj); - public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(TagDefinition tagDef); - public TagDefinition[] TagsThatImply(Guid tagDef); - public TagDefinition[] TagsThatImply(TagDefinition tagDef); -} - -public class TagService : ITagService { - private IDbContextFactory dbFactory; - - public TagService(IDbContextFactory dbFactory) => - this.dbFactory = dbFactory; - - public void AddTag(Guid obj, Guid tagDef) { - using var db = dbFactory.CreateDbContext(); - - var tag = db.TagDefinitions.First(td => td.Guid == tagDef); - - db.Objects - .Include(o => o.Tags) - .ThenInclude(t => t.TagDefinition) - .Where(o => !o.Tags.Select(t => t.TagDefinition.Guid).Contains(tagDef)) - .FirstOrDefault(o => o.Guid == obj)? - .Tags - .Add(new(tag)); - - db.SaveChanges(); - } - - public void AddTag(HBObject obj, TagDefinition tagDef) => - AddTag(obj.Guid, tagDef.Guid); - - public void RemoveTag(Guid obj, Guid tagDef) { - using var db = dbFactory.CreateDbContext(); - - db.Objects - .Include(o => o.Tags) - .ThenInclude(t => t.TagDefinition) - .First(o => o.Guid == obj) - .Tags - .RemoveAll(t => t.TagDefinition.Guid == tagDef); - - db.SaveChanges(); - } - - public void RemoveTag(HBObject obj, TagDefinition tagDef) => - RemoveTag(obj.Guid, tagDef.Guid); - - public void SetImplicitTags(Guid tagDef, Guid[] implicitTagDefs) { - using var db = dbFactory.CreateDbContext(); - using var transaction = db.Database.BeginTransaction(); - - var tag = db.TagDefinitions - .Include(td => td.ImplicitTags) - .First(td => td.Guid == tagDef); - - tag.ImplicitTags.RemoveAll(td => !implicitTagDefs.Contains(td.Guid)); - tag.ImplicitTags.AddRange( - db.TagDefinitions - .Where(td => implicitTagDefs.Contains(td.Guid)) - .Where(td => !tag.ImplicitTags - .Select(td => td.Guid) - .Contains(td.Guid))); - - db.SaveChanges(); - transaction.Commit(); - } - - public void SetImplicitTags(TagDefinition tagDef, TagDefinition[] implicitTagDefs) => - SetImplicitTags(tagDef.Guid, implicitTagDefs.Select(td => td.Guid).ToArray()); - - public void AddImplicitTag(Guid tagDef, Guid implicitTagDef) { - using var db = dbFactory.CreateDbContext(); - - var tag = db.TagDefinitions - .Include(td => td.ImplicitTags) - .First(td => td.Guid == tagDef); - var implicitTag = db.TagDefinitions.First(td => td.Guid == implicitTagDef); - - tag.ImplicitTags.Add(implicitTag); - db.SaveChanges(); - } - - public void AddImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef) => - AddImplicitTag(tagDef, implicitTagDef); - - public void RemoveImplicitTag(Guid tagDef, Guid implicitTagDef) { - using var db = dbFactory.CreateDbContext(); - - var tag = db.TagDefinitions - .Include(td => td.ImplicitTags) - .First(td => td.Guid == tagDef); - - tag.ImplicitTags.RemoveAll(td => td.Guid == implicitTagDef); - db.SaveChanges(); - } - - public void RemoveImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef) => - RemoveImplicitTag(tagDef, implicitTagDef); - - public void CreateTagDefinition(string name, string? @namespace = null, string? alias = null) { - using var db = dbFactory.CreateDbContext(); - - if(string.IsNullOrEmpty(@namespace)) - @namespace = null; - if(string.IsNullOrEmpty(alias)) - alias = null; - - // Remove leading and trailing whitespace - name = name.Trim(); - @namespace = @namespace?.Trim(); - alias = alias?.Trim(); - - TagDefinition tagDef = new() { - Source = TagSource.UserTag, - Namespace = @namespace, - Name = name, - Alias = alias - }; - - bool nameExists = db.TagDefinitions.Any(td => td.Name.ToLower() == name.ToLower()); - bool aliasExists = false; - if(alias is not null) - aliasExists = db.TagDefinitions - .Where(td => td.Alias != null) - .Any(td => td.Alias!.ToLower() == alias.ToLower()); - if(nameExists || aliasExists) - throw new TagDuplicateException(nameExists, aliasExists); - - if(!db.TagDefinitions.Contains(tagDef)) - db.TagDefinitions.Add(tagDef); - db.SaveChanges(); - } - - public void DeleteTagDefinition(Guid tagDef) { - using var db = dbFactory.CreateDbContext(); - - var tag = db.TagDefinitions.First(td => td.Guid == tagDef); - - using var transaction = db.Database.BeginTransaction(); - - db.Tags.RemoveRange( - db.Tags - .Include(t => t.TagDefinition) - .Where(t => t.TagDefinition.Guid == tagDef)); - db.TagDefinitions.Remove(tag); - db.SaveChanges(); - - transaction.Commit(); - } - - public void DeleteTagDefinition(TagDefinition tagDef) => - DeleteTagDefinition(tagDef.Guid); - - public void UpdateTagDefinition(Guid tagDef, string name, string? @namespace = null, string? alias = null) { - using var db = dbFactory.CreateDbContext(); - - if(string.IsNullOrEmpty(@namespace)) - @namespace = null; - if(string.IsNullOrEmpty(alias)) - alias = null; - - // Remove leading and trailing whitespace - name = name.Trim(); - @namespace = @namespace?.Trim(); - alias = alias?.Trim(); - - var tag = db.TagDefinitions.First(td => td.Guid == tagDef); - - TagDefinition? nameExisting = db.TagDefinitions.FirstOrDefault(td => td.Name.ToLower() == name.ToLower()); - TagDefinition? aliasExisting = null; - if(alias is not null) - aliasExisting = db.TagDefinitions - .Where(td => td.Alias != null) - .FirstOrDefault(td => td.Alias!.ToLower() == alias.ToLower()); - bool nameExists = nameExisting is not null && nameExisting != tag; - bool aliasExists = aliasExisting is not null && aliasExisting != tag; - if(nameExists || aliasExists) - throw new TagDuplicateException(nameExists, aliasExists); - - tag.Name = name; - tag.Namespace = @namespace; - tag.Alias = alias; - - db.SaveChanges(); - } - - public void UpdateTagDefinition(TagDefinition tagDef, string name, string? @namespace = null, string? alias = null) => - UpdateTagDefinition(tagDef.Guid, name, @namespace, alias); - - private (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(IEnumerable tagDefs) { - using var db = dbFactory.CreateDbContext(); - - var tagGuids = tagDefs - .Select(td => td.Guid) - .ToArray(); - - // Query all tag definitions - var allTags = db.TagDefinitions - .Include(td => td.ImplicitTags) - .ToArray(); - - var tags = new List( - allTags.IntersectBy( - tagGuids, - td => td.Guid)); - - while(true) { - var toAdd = tags - .SelectMany(td => td.ImplicitTags) - .ExceptBy(tags.Select(td => td.Guid), td => td.Guid) - .ToArray(); - - if(toAdd.Count() == 0) - break; - - tags.AddRange(toAdd); - } - - return tags - .Select(td => new ValueTuple(td, !tagGuids.Contains(td.Guid))) - .ToArray(); - } - - public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(Guid obj) { - using var db = dbFactory.CreateDbContext(); - - // Query a list of tag GUIDs for this object - var tags = db.Objects - .Include(o => o.Tags) - .ThenInclude(t => t.TagDefinition) - .First(o => o.Guid == obj) - .Tags - .Select(t => t.TagDefinition) - .ToArray(); - - return GetAllTags(tags); - } - - - public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(HBObject obj) => - GetAllTags(obj.Guid); - - public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(TagDefinition tagDef) { - using var db = dbFactory.CreateDbContext(); - - var tags = db.TagDefinitions - .Include(td => td.ImplicitTags) - .First(td => td.Guid == tagDef.Guid) - .ImplicitTags - .ToArray(); - - return GetAllTags(tags); - } - - public TagDefinition[] TagsThatImply(Guid tagDef) { - using var db = dbFactory.CreateDbContext(); - - var tagDefs = db.TagDefinitions - .Include(td => td.ImplicitTags) - .ToArray(); - - var tags = new List() { - db.TagDefinitions.First(td => td.Guid == tagDef) - }; - - while(true) { - var toAdd = tagDefs - .Where(td => td.ImplicitTags.Select(it => it.Guid).Intersect(tags.Select(td => td.Guid)).Any()) - .ExceptBy(tags.Select(td => td.Guid), td => td.Guid) - .ToArray(); - - if(toAdd.Count() == 0) - break; - - tags.AddRange(toAdd); - } - - return tags.ToArray(); - } - - public TagDefinition[] TagsThatImply(TagDefinition tagDef) => - TagsThatImply(tagDef.Guid); -} diff --git a/Services/UserService.cs b/Services/UserService.cs deleted file mode 100644 index 9e79dc6..0000000 --- a/Services/UserService.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.AspNetCore.Cryptography.KeyDerivation; - -namespace HyperBooru.Services; - -public interface IUserService { - public UserSessionState UserSessionState { get; } -} - -public class UserService : IUserService { - public UserSessionState UserSessionState => - globalUserService.GetSessionState(httpContext.Session.Id); - - private IHttpContextAccessor httpContextAccessor; - private IGlobalUserService globalUserService; - - private HttpContext httpContext => - httpContextAccessor.HttpContext!; - - public UserService( - IHttpContextAccessor httpContextAccessor, - IGlobalUserService globalUserService) { - - this.httpContextAccessor = httpContextAccessor; - this.globalUserService = globalUserService; - - // HTTP context session states are discarded if no values - // are set. Set a dummy value so that the session state - // will not be discarded later when we actually need it. - httpContext.Session.SetInt32("Persist", 1); - } - - public static string HashPassword(string password) => - Convert.ToBase64String( - KeyDerivation.Pbkdf2( - password, - Array.Empty(), - KeyDerivationPrf.HMACSHA512, - 100_000, - 512 / 8)); -} - -public interface IGlobalUserService { - public UserSessionState GetSessionState(string id); -} - -public class GlobalUserService : IGlobalUserService { - // TODO: prune this list periodically - private Dictionary sessionStates = new(); - - public UserSessionState GetSessionState(string id) { - sessionStates.TryGetValue(id, out var state); - - if(state is null) { - state = new(); - sessionStates[id] = state; - } - - return state; - } -} - -public record UserSessionState { - public event UserSessionStateChange OnStateChange; - - public bool ShowNsfw { - get => showNsfw; - set { - showNsfw = value; - OnStateChange.Invoke(this); - } - } - - private bool showNsfw = false; -} - -public delegate void UserSessionStateChange(UserSessionState sessionState); diff --git a/Tag.cs b/Tag.cs deleted file mode 100644 index 7da6232..0000000 --- a/Tag.cs +++ /dev/null @@ -1,34 +0,0 @@ -using HyperBooru.ApiModels; -using System.ComponentModel.DataAnnotations.Schema; - -namespace HyperBooru; - -public class TagDefinition : HBObject { - public TagSource Source { get; set; } = TagSource.Internal; - public string? Namespace { get; set; } - public string Name { get; set; } - public string? Alias { get; set; } - public virtual List ImplicitTags { get; set; } = new(); - - public static explicit operator ApiModels.TagDefinition(TagDefinition tagDefinition) => new() { - TagDefinitionId = tagDefinition.Guid, - Source = tagDefinition.Source, - Namespace = tagDefinition.Namespace, - Name = tagDefinition.Name, - Alias = tagDefinition.Alias, - ImplicitTags = tagDefinition.ImplicitTags.Select(td => td.Guid).ToArray() - }; -} - -public class Tag : HBObject { - [ForeignKey("ObjectId")] - public int TagDefinitionId { get; set; } - public virtual TagDefinition TagDefinition { get; set; } - public DateTime CreateTime { get; set; } = DateTime.UtcNow; - public virtual HBObject Target { get; set; } - - public Tag() {} - - public Tag(TagDefinition tagDef) => - this.TagDefinition = tagDef; -} diff --git a/Todo.md b/Todo.md deleted file mode 100644 index 612f60e..0000000 --- a/Todo.md +++ /dev/null @@ -1,45 +0,0 @@ -# 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 - - [X] Autocorrect needs to be disabled on inputs such as username, tag name, etc - - [X] Mobile menu does not automatically hide upon page navigation - - [X] Input not focused - - [ ] Setting implicit tags removes builtin tags - - [X] UserService listeners don't seem to be removed after disposal - - [X] Cancelling tag creation creates the tag anyway - - [ ] Prevent marking tagging complete unless there are actually user tags - - [X] Media upload not deduping media - - [ ] Can't delete media - -# Short-term Features - - [ ] Ability to set password (at least via API) - - [ ] PowerShell uploading with initial tagging - - [ ] Proper thumbnail generation - - [ ] Video support - - [ ] User/security support - - [X] Record in UploadedFiles whether the checksums was verified at upload time - - [X] Record in Media which UploadedFile actually holds the current content - -# Long-term Features - - [ ] Redirect to last page after login - - [ ] Enlarge image view in ViewMedia - - [ ] Periodic thumbnail scrubbing - - [ ] Loading animations - - [ ] Keyboard shortcuts - - [ ] Find source - - [ ] Collections - - [ ] Search memes by audio (for some reason) - - [ ] Jump into ingest feed at random point - - [ ] Rating system - - [ ] Instantaneous OCR processing when media is uploaded - - [ ] OCR status reporting on admin page - - [ ] Dynamically update OCR data on ViewMedia page - - [ ] Image deduplication by visual similarity - - [ ] Audit log - - [ ] Journaled operations - - [ ] Confirmation dialog before enabling NSFW mode - - [ ] Upload progress bars diff --git a/User.cs b/User.cs deleted file mode 100644 index 87384d2..0000000 --- a/User.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace HyperBooru; - -[Index(nameof(Username))] -public class User : HBObject { - public string Username { get; set; } - public string PasswordHash { get; set; } - - public static explicit operator ApiModels.User(User user) => - new() { - UserId = user.Guid, - Username = user.Username - }; -} diff --git a/tessdata/eng.traineddata b/tessdata/eng.traineddata deleted file mode 100644 index 176dc32..0000000 Binary files a/tessdata/eng.traineddata and /dev/null differ diff --git a/wwwroot/css/site.css b/wwwroot/css/site.css deleted file mode 100644 index 21f9a94..0000000 --- a/wwwroot/css/site.css +++ /dev/null @@ -1,28 +0,0 @@ -#blazor-error-ui { - background: #555; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - -#blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 3.5rem; - top: 0.5rem; -} - -.blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; - color: white; - padding: 1rem 1rem 1rem 3.7rem; -} - -.blazor-error-boundary::after { - content: "An error has occurred." -} diff --git a/wwwroot/favicon.ico b/wwwroot/favicon.ico deleted file mode 100644 index a1be4cc..0000000 Binary files a/wwwroot/favicon.ico and /dev/null differ diff --git a/wwwroot/icon-192.png b/wwwroot/icon-192.png deleted file mode 100644 index 28ce06d..0000000 Binary files a/wwwroot/icon-192.png and /dev/null differ diff --git a/wwwroot/icon-512.png b/wwwroot/icon-512.png deleted file mode 100644 index 8c28696..0000000 Binary files a/wwwroot/icon-512.png and /dev/null differ diff --git a/wwwroot/images/book.svg b/wwwroot/images/book.svg deleted file mode 100644 index 6cdfc79..0000000 --- a/wwwroot/images/book.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wwwroot/images/checkmark.svg b/wwwroot/images/checkmark.svg deleted file mode 100644 index 5e55d9e..0000000 --- a/wwwroot/images/checkmark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wwwroot/images/cross.svg b/wwwroot/images/cross.svg deleted file mode 100644 index 0c37363..0000000 --- a/wwwroot/images/cross.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wwwroot/images/edit.svg b/wwwroot/images/edit.svg deleted file mode 100644 index d4c6ec4..0000000 --- a/wwwroot/images/edit.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - alt-square-pencil - \ No newline at end of file diff --git a/wwwroot/images/info.svg b/wwwroot/images/info.svg deleted file mode 100644 index b194f05..0000000 --- a/wwwroot/images/info.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - diff --git a/wwwroot/images/loginbg.webp b/wwwroot/images/loginbg.webp deleted file mode 100644 index 759e666..0000000 Binary files a/wwwroot/images/loginbg.webp and /dev/null differ diff --git a/wwwroot/images/photo.svg b/wwwroot/images/photo.svg deleted file mode 100644 index 486c360..0000000 --- a/wwwroot/images/photo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - image - \ No newline at end of file diff --git a/wwwroot/images/tag.svg b/wwwroot/images/tag.svg deleted file mode 100644 index 3eb8843..0000000 --- a/wwwroot/images/tag.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wwwroot/images/trash.svg b/wwwroot/images/trash.svg deleted file mode 100644 index 18ff9c1..0000000 --- a/wwwroot/images/trash.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wwwroot/js/dialog.js b/wwwroot/js/dialog.js deleted file mode 100644 index 418962f..0000000 --- a/wwwroot/js/dialog.js +++ /dev/null @@ -1,78 +0,0 @@ -function dialogMouseDown(e) { - bumpDialog(e.currentTarget); -} - -function dialogTitleMouseDown(e) { - e = e || window.event; - e.preventDefault(); - var element = e.currentTarget.parentElement; - var ds = element.dataset; - ds.lastX = e.clientX; - ds.lastY = e.clientY; - - window.dragDialog = element; - document.onmouseup = dragMouseUp; - document.onmousemove = dragMouseMove; -} - -function dragMouseUp() { - window.dragDialog = null; - document.onmouseup = null; - document.onmousemove = null; -} - -function dragMouseMove(e) { - e = e || window.event; - e.preventDefault(); - var element = window.dragDialog; - var ds = element.dataset; - deltaX = ds.lastX - e.clientX; - deltaY = ds.lastY - e.clientY; - ds.lastX = e.clientX; - ds.lastY = e.clientY; - element.style.left = (element.offsetLeft - deltaX) + 'px'; - element.style.top = (element.offsetTop - deltaY) + 'px'; -} - -function setDialogVisibility(element, visible) { - if(visible) { - element.style.left = null; - element.style.top = null; - element.style.opacity = 1; - element.style.visibility = 'visible'; - bumpDialog(element); - - var input = element.querySelector('input[type="text"]'); - if(input) { - setTimeout(() => input.focus(), 100); - } - } else { - element.style.opacity = 0; - element.style.visibility = 'hidden'; - } -} - -function bumpDialog(element) { - var dialogs = Array - .from(document.querySelectorAll('div.dialog')) - .map(e => ({ zIndex: parseInt(e.style.zIndex), element: e })) - .sort((a, b) => a.zIndex - b.zIndex) - .map(d => d.element) - .filter(e => e != element); - - dialogs.push(element); - - var z = 900; - for(var d of dialogs) - d.style.zIndex = z++; -} - -function dialogAddObjectReference(element, dialogObject) { - if(!window.dialogObjects) - window.dialogObjects = [] - - window.dialogObjects.push({ - element: element, - dialogObject: dialogObject - }); -} diff --git a/wwwroot/js/keyboard.js b/wwwroot/js/keyboard.js deleted file mode 100644 index 8b46639..0000000 --- a/wwwroot/js/keyboard.js +++ /dev/null @@ -1,57 +0,0 @@ -async function keyDownHandler(e) { - function isDialogChild(e) { - while(e = e.parentElement) - if(e.tagName == 'DIV' && e.classList.contains('dialog')) - return true; - return false; - } - - var tag = document.activeElement.tagName; - if((tag == 'INPUT' || (tag == 'TEXTAREA' && e.ctrlKey)) && e.key == 'Enter') { - var element = document.activeElement; - while(element = element.parentElement) { - if(element.tagName == 'FORM') { - element - .querySelectorAll('input,textarea') - .forEach(e => e.dispatchEvent(new Event('change'))); - element.requestSubmit(); - e.preventDefault(); - return; - } - } - } - - if((tag == 'INPUT' || tag == 'TEXTAREA') && e.key != 'Escape') - return; - - var element = Array.from(document.querySelectorAll('div.dialog')) - .filter(e => e.style.visibility == 'visible') - .map(e => ({ element: e, zIndex: parseInt(e.style.zIndex) })) - .sort((a, b) => b.zIndex - a.zIndex) - .map(e => e.element)[0]; - - if(element) { - await window.dialogObjects - .find(d => d.element == element) - .dialogObject - .invokeMethodAsync('KeyHandler', e.key); - e.preventDefault(); - return; - } - - var button = Array.from(document.getElementsByTagName('button')) - .filter(b => typeof(b.dataset.keyboardShortcut) == 'string') - .filter(b => !isDialogChild(b)) - .find(b => b.dataset.keyboardShortcut == e.key); - - if(!e.ctrlKey && button) { - button.click(); - e.preventDefault(); - return; - } - - if(typeof pageKeyDownHandler == 'function') - pageKeyDownHandler(e); -} - -window.onload = () => document.onkeydown = keyDownHandler; \ No newline at end of file diff --git a/wwwroot/js/mobile.js b/wwwroot/js/mobile.js deleted file mode 100644 index 0af11cc..0000000 --- a/wwwroot/js/mobile.js +++ /dev/null @@ -1,7 +0,0 @@ -function hideMobileMenu() { - document.getElementsByTagName('body')[0].classList.remove('mobile-menu-visible'); -} - -function toggleMobileMenu() { - document.getElementsByTagName('body')[0].classList.toggle('mobile-menu-visible'); -} diff --git a/wwwroot/loginbg.webm b/wwwroot/loginbg.webm deleted file mode 100644 index 139ed0d..0000000 Binary files a/wwwroot/loginbg.webm and /dev/null differ diff --git a/wwwroot/manifest.webmanifest b/wwwroot/manifest.webmanifest deleted file mode 100644 index f150f98..0000000 --- a/wwwroot/manifest.webmanifest +++ /dev/null @@ -1,6 +0,0 @@ -{ - "icons": [ - { "src": "/icon-192.png", "type": "images/png", "sizes": "192x192" }, - { "src": "/icon-512.png", "type": "images/png", "sizes": "512x512" } - ] -} \ No newline at end of file diff --git a/wwwroot/styles/data-table.css b/wwwroot/styles/data-table.css deleted file mode 100644 index 994d625..0000000 --- a/wwwroot/styles/data-table.css +++ /dev/null @@ -1,21 +0,0 @@ -table.data-table { - border-collapse: collapse; - width: 100%; -} - -table.data-table > tr > th { - border-bottom: 1px solid white; - padding: 4px; -} - -table.data-table > tr > td { - padding: 4px; -} - -table.data-table > tr:nth-child(2n) { - background: rgba(255, 255, 255, 0.1); -} - -table.data-table > tr > td:not(:last-child) { - border-right: 1px solid white; -} diff --git a/wwwroot/styles/global.css b/wwwroot/styles/global.css deleted file mode 100644 index 9de9fc1..0000000 --- a/wwwroot/styles/global.css +++ /dev/null @@ -1,214 +0,0 @@ -@import url('data-table.css'); - -:root { - --col-accent-pri: #0aa; - --col-accent-pri-hl: #0cc; - --col-error-pri: #ffaa00; - --col-checksum-verified-pri: #8dff76; - --col-bg: #222; - --col-dialog-bg: #333; - --col-navbar-bg: var(--col-accent-pri); - --col-button-pri: var(--col-accent-pri); - --col-button-pri-hl: var(--col-accent-pri-hl); - --col-button-disabled: #777; - --col-button-disabled-bg: #444; - --col-button-sec: #555; - --col-button-sec-hl: #777; - --col-button-sec-disabled: #555; - --col-button-sec-disabled-bg: #000; - --col-button-warning: #ff4848; - --col-button-warning-hl: #ff9999; - --col-hr: #888; - --col-scrollbar: #666; - --col-scrollbar-hover: #aaaaaa; - --col-switch-bg: var(--col-bg); - --col-switch-fg: #fff; - --col-switch-bg-hl: var(--col-accent-pri); - --col-progbar-fg: var(--col-accent-pri); - --col-progbar-bg: #777; - - --size-default-gap: 30px; -} - -::selection { - background: var(--col-accent-pri); -} - -body { - background: var(--col-bg); - color: white; - display: flex; - flex-direction: column; - font-family: 'Trebuchet MS', 'Lucida Sans Unicode'; - height: 100dvh; - margin: 0; - overflow: hidden; - width: 100dvw; -} - -a { - color: var(--col-accent-pri); - text-decoration: none; -} - -@media (hover: hover) { - a:hover { - filter: brightness(1.5); - } -} - -a::selection { - background: var(--col-accent-pri); - color: #fff; -} - -a.nondecorated { - color: #fff; -} - -@media (hover: hover) { - a.nondecorated:hover { - color: #999; - } -} - -code { - background: #222; - border-radius: 10px; - box-sizing: border-box; - font-family: 'Lucida Console'; - font-size: 8pt; - overflow-y: auto; - padding: 20px; - white-space: pre-line; -} - -button, input[type=submit] { - align-items: center; - background: var(--col-button-pri); - border-radius: 10px; - border: none; - box-sizing: border-box; - color: white; - cursor: pointer; - display: flex; - height: 30px; - margin: 10px 5px 0 5px; - padding: 0 9px 0 9px; - user-select: none; -} - -button:disabled { - color: var(--col-button-disabled) !important; - background: var(--col-button-disabled-bg) !important; -} - -button.warning { - background: var(--col-button-warning); -} - -button > img { - height: 15px; - margin-right: 5px; - width: 15px; -} - -@media (hover: none) and (pointer: coarse) { - button > :not(:first-child) { - display: none; - } - - button > img { - height: 20px; - margin-right: 0; - padding: 8px; - width: 20px; - } -} - -@media (hover: hover) { - button.warning:hover { - background: var(--col-button-warning-hl); - } -} - -button.warning:active { - color: var(--col-button-warning); - background: white; -} - -button.secondary { - background: var(--col-button-sec); -} - -@media (hover: hover) { - button.secondary:hover { - background: var(--col-button-sec-hl); - } -} - -button.secondary:active { - background: white; - color: var(--col-button-sec); -} - -button.secondary:disabled { - color: var(--col-button-sec-disabled) !important; - background: var(--col-button-sec-disabled-bg) !important; -} - -@media (hover: hover) { - button:hover, input[type=submit]:hover { - background: var(--col-button-pri-hl); - } -} - -button:active, input[type=submit]:active { - background: white; - color: var(--col-button-pri); -} - -input, textarea { - background: rgba(0, 0, 0, 0); - border-radius: 5px; - border: 1px solid #aaa; - box-sizing: border-box; - color: white; - margin-bottom: 10px; -} - -input { - height: 25px !important; -} - -/* disable hotkey underlines on mobile devices */ -@media (hover: none) and (pointer: coarse) { - button > u { - text-decoration: none !important; - } -} - -/* necessary for use inside flex containers */ -hr { - width: 100%; -} - -::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -::-webkit-scrollbar-thumb { - background: var(--col-scrollbar); - border-radius: 10px; -} - -@media (hover: hover) { - ::-webkit-scrollbar-thumb:hover { - background: var(--col-scrollbar-hover); - } -} - -::-webkit-scrollbar-corner { - opacity: 0; -} -- cgit v1.3