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); return tagDefinition is not null ? Ok(tagDefinition) : NotFound(); } [HttpPost("definition")] public async Task CreateTagDefinitionAsync([FromBody] TagCreateRequest request) { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); if(db.TagDefinitions.Any(td => td.Name == request.Name)) return BadRequest("Name already exists"); if(request.Alias is not null) if(db.TagDefinitions.Any(td => td.Alias == request.Alias)) return BadRequest("Alias already exists"); List 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) return NotFound("Tag definition not found"); if(tagDefinition.ObjectId < 0) return BadRequest("Cannot delete built-in tag definition"); db.TagDefinitions.Remove(tagDefinition); await db.SaveChangesAsync(); await transaction.CommitAsync(); return Ok(); } [HttpPatch("definition/{tagDefinitionId}")] public async Task UpdateTagDefinitionAsync( [FromRoute] Guid tagDefinitionId, [FromBody] TagUpdateRequest request) { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); var tagDefinition = await db.TagDefinitions .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) return NotFound("Tag definition not found"); if(tagDefinition.ObjectId < 0) return BadRequest("Cannot update built-in tag definition"); if(request.Name is not null) if(db.TagDefinitions.Any(td => td.Name == request.Name)) return BadRequest("Name already exists"); if(request.Alias is not null) if(db.TagDefinitions.Any(td => td.Alias == request.Alias)) return BadRequest("Alias already exists"); tagDefinition.Namespace = request.Namespace ?? tagDefinition.Namespace; tagDefinition.Name = request.Name ?? tagDefinition.Name; tagDefinition.Alias = request.Alias ?? tagDefinition.Alias; await db.SaveChangesAsync(); await transaction.CommitAsync(); return Ok((ApiModels.TagDefinition) tagDefinition); } [HttpPatch("definition/{tagDefinitionId}/implicit")] public async Task AddImplicitTagsAsync( [FromRoute] Guid tagDefinitionId, [FromBody] Guid[] implicitTagIds) { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); var tagDefinition = await db.TagDefinitions .Include(td => td.ImplicitTags) .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) return NotFound("Tag definition not found"); if(tagDefinition.ObjectId < 0) return BadRequest("Cannot update built-in tag definition"); implicitTagIds = implicitTagIds.Distinct().ToArray(); var implicitTags = db.TagDefinitions .Where(td => implicitTagIds.Contains(td.Guid)) .ToArray(); var missingTags = implicitTagIds.Except(implicitTags.Select(td => td.Guid)); var missingTagsString = string.Join(", ", missingTags.Select(td => td.ToString())); if(missingTags.Any()) return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); tagDefinition.ImplicitTags.AddRange( implicitTags.ExceptBy(tagDefinition.ImplicitTags.Select(td => td.Guid), td => td.Guid)); await db.SaveChangesAsync(); await transaction.CommitAsync(); return Ok(); } [HttpPut("definition/{tagDefinitionId}/implicit")] public async Task ReplaceImplicitTagsAsync( [FromRoute] Guid tagDefinitionId, [FromBody] Guid[] implicitTagIds) { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); var tagDefinition = await db.TagDefinitions .Include(td => td.ImplicitTags) .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) return NotFound("Tag definition not found"); if(tagDefinition.ObjectId < 0) return BadRequest("Cannot update built-in tag definition"); implicitTagIds = implicitTagIds.Distinct().ToArray(); var implicitTags = db.TagDefinitions .Where(td => implicitTagIds.Contains(td.Guid)) .ToArray(); var missingTags = implicitTagIds.Except(implicitTags.Select(td => td.Guid)); var missingTagsString = string.Join(", ", missingTags.Select(td => td.ToString())); if(missingTags.Any()) return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); tagDefinition.ImplicitTags.AddRange( implicitTags.ExceptBy(tagDefinition.ImplicitTags.Select(td => td.Guid), td => td.Guid)); var toRemove = tagDefinition.ImplicitTags .Where(td => !implicitTags.Select(td => td.Guid).Contains(td.Guid)) .ToArray(); foreach(var td in toRemove) tagDefinition.ImplicitTags.Remove(td); await db.SaveChangesAsync(); await transaction.CommitAsync(); return Ok(); } [HttpPost("definition/{tagDefinitionId}/implicit/delete")] public async Task DeleteImplicitTagAsync( [FromRoute] Guid tagDefinitionId, [FromBody] Guid[] implicitTagIds) { using var db = dbFactory.CreateDbContext(); using var transaction = await db.Database.BeginTransactionAsync(); var tagDefinition = await db.TagDefinitions .Include(td => td.ImplicitTags) .FirstOrDefaultAsync(td => td.Guid == tagDefinitionId); if(tagDefinition is null) return NotFound("Tag definition not found"); if(tagDefinition.ObjectId < 0) return BadRequest("Cannot update built-in tag definition"); implicitTagIds = implicitTagIds.Distinct().ToArray(); var missingTagIds = implicitTagIds .Except(tagDefinition.ImplicitTags.Select(td => td.Guid)); var missingTagsString = string.Join(", ", missingTagIds.Select(td => td.ToString())); if(missingTagIds.Any()) return BadRequest($"Invalid tag IDs specified: {missingTagsString}"); var toRemove = tagDefinition.ImplicitTags .Where(td => !implicitTagIds.Contains(td.Guid)) .ToArray(); foreach(var td in toRemove) tagDefinition.ImplicitTags.Remove(td); await db.SaveChangesAsync(); await transaction.CommitAsync(); return Ok(); } }