{"record":{"id":"64f704d20fc532fa","repo":"Kareadita/Kavita","slug":"collection-tag-title-required","errorCode":null,"errorMessage":"collection-tag-title-required","messagePattern":"collection-tag-title-required","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"warning","filePath":"Kavita.Services/CollectionTagService.cs","lineNumber":41,"sourceCode":"        var collectionTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(tagId, ct: ct);\n        if (collectionTag == null) return true;\n\n        user.Collections.Remove(collectionTag);\n\n        if (!unitOfWork.HasChanges()) return true;\n\n        return await unitOfWork.CommitAsync(ct);\n    }\n\n\n    public async Task<bool> UpdateTag(AppUserCollectionDto dto, int userId, CancellationToken ct = default)\n    {\n        var existingTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(dto.Id, ct: ct);\n        if (existingTag == null) throw new KavitaException(\"collection-doesnt-exist\");\n        if (existingTag.AppUserId != userId) throw new KavitaException(\"access-denied\");\n\n        var title = dto.Title.Trim();\n        if (string.IsNullOrEmpty(title)) throw new KavitaException(\"collection-tag-title-required\");\n\n        // Ensure the title doesn't exist on the user's account already\n        if (!title.Equals(existingTag.Title) && await unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, userId, ct))\n            throw new KavitaException(\"collection-tag-duplicate\");\n\n        existingTag.Items ??= [];\n        if (existingTag.Source == ScrobbleProvider.Kavita)\n        {\n            existingTag.Title = title;\n            existingTag.NormalizedTitle = dto.Title.ToNormalized();\n        }\n\n        var roles = await unitOfWork.UserRepository.GetRoles(userId, ct);\n        if (roles.Contains(PolicyConstants.AdminRole) || roles.Contains(PolicyConstants.PromoteRole))\n        {\n            existingTag.Promoted = dto.Promoted;\n        }\n        existingTag.CoverImageLocked = dto.CoverImageLocked;","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/CollectionTagService.cs#L23-L59","documentation":"Thrown by UpdateTag when the title, after trimming, is null or empty. The check 'string.IsNullOrEmpty(title)' fires after 'dto.Title.Trim()'. This is pure input validation — the collection exists (checked before) and ownership passed, but the submitted title is blank.","triggerScenarios":"User submits an empty title or a title consisting only of whitespace characters; form validation on the client side failed to prevent an empty submission; API call constructed programmatically with an empty title field.","commonSituations":"Client-side validation was bypassed or is incomplete; user cleared the title field and submitted without the UI catching it; automated/scripted requests with malformed payloads.","solutions":["Add client-side validation to prevent empty title submissions (required attribute on the input field)","Validate the title is non-empty in the controller before calling UpdateTag","Ensure the DTO has [Required] data annotation on the Title property"],"exampleFix":"// Add DTO validation:\n// public class AppUserCollectionDto\n// {\n//     [Required(ErrorMessage = \"Title is required\")]\n//     [MinLength(1)]\n//     public string Title { get; set; } = string.Empty;\n// }","handlingStrategy":"validation","validationCode":"// Validate title before calling UpdateTag:\n// var trimmedTitle = dto.Title?.Trim();\n// if (string.IsNullOrEmpty(trimmedTitle)) {\n//     return BadRequest(\"Collection title is required\");\n// }","typeGuard":null,"tryCatchPattern":"// try {\n//     await collectionTagService.UpdateTag(dto, userId, ct);\n// } catch (KavitaException ex) when (ex.Message == \"collection-tag-title-required\") {\n//     return BadRequest(new { error = \"A non-empty title is required.\" });\n// }","preventionTips":["Add [Required] and [MinLength(1)] data annotations on the DTO Title property","Implement client-side form validation to prevent empty submissions","Disable the submit button until the title field has non-whitespace content"],"tags":["collection","validation","title","input-validation"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}