< Summary

Information
Class: Api.Works.Mutations.UpdateTagInput.UpdateTagInputValidator
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Mutations/UpdateTagMutation.cs
Line coverage
100%
Covered lines: 43
Uncovered lines: 0
Coverable lines: 43
Total lines: 129
Line coverage: 100%
Branch coverage
62%
Covered branches: 5
Total branches: 8
Branch coverage: 62.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
<-ctor()62.5%88100%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Mutations/UpdateTagMutation.cs

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik <[email protected]>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5// Mostly Ai Generated - Start
 6using Api.Auth.Handlers;
 7using Api.Auth.Models;
 8using Api.Auth.Utils;
 9using Api.Database;
 10using Api.Database.Utils;
 11using Api.Utils;
 12using Api.Works.Models;
 13using Api.Works.Queries;
 14using FluentValidation;
 15using Microsoft.EntityFrameworkCore;
 16using NodaTime;
 17using Npgsql;
 18
 19namespace Api.Works.Mutations;
 20
 21[MutationType]
 22public static partial class UpdateTagMutations
 23{
 24    [PermissionCheckAuthorize(UserPermissionBits.TagRead | UserPermissionBits.TagWrite)]
 25    [Error(typeof(DomainError))]
 26    public static async Task<UpdateTagPayload> UpdateTagAsync(
 27        [Service] PGContext db,
 28        [Service] IEFTransactionDIAccessorService txGetter,
 29        [Service] IValidator<UpdateTagInput> validator,
 30        UpdateTagInput input,
 31        CancellationToken ct
 32    )
 33    {
 34        await validator.ValidateOrThrowInputAsync(input, ct);
 35
 36        var tx = await txGetter.BeginOrGetTransactionAsync();
 37        var tag = await db.WorkTags.SingleAsync(
 38            t => t.Id == input.Id,
 39            cancellationToken: ct
 40        );
 41
 42        UpdateTagInput.ApplyToTag(tag, input);
 43        tag.RowVersion += 1;
 44        tag.MetadataUpdatedAt = Now();
 45
 46        await db.SaveChangesOrThrowAsync(
 47            PostgresErrorCodes.UniqueViolation,
 48            ErrorCodes.TAG_ALREADY_EXISTS,
 49            "A tag with the same namespace and name already exists",
 50            ct
 51        );
 52
 53        await tx.CommitAsync(ct);
 54        return new UpdateTagPayload(TagMapper.ToDto(tag));
 55    }
 56}
 57
 58public record UpdateTagPayload(Queries.Tag Tag);
 59
 60public record UpdateTagInput : IBasicEntityMetadata
 61{
 62    public static byte IdPostfix => Models.WorkTag.IdPostfix;
 63
 64    public Guid Id { get; init; }
 65    public int RowVersion { get; init; }
 66
 67    public required Optional<string[]> TagNamespace { get; init; }
 68    public required Optional<string> TagName { get; init; }
 69
 70    public class UpdateTagInputValidator : AbstractValidator<UpdateTagInput>
 71    {
 172        public UpdateTagInputValidator(
 173            PGContext db,
 174            ICurrentUserId userId,
 175            IEFTransactionDIAccessorService tx
 176        )
 177        {
 178            RuleFor(w => w).BeginTransaction(tx);
 79
 180            RuleFor(w => w.Id).IdMustExist(db.WorkTags, userId.Id);
 81
 182            RuleFor(w => w.RowVersion).RowVersionMustMatch(db.WorkTags);
 83
 184            RuleFor(w => w)
 185                .MustAsync(
 186                    async (input, ct) =>
 187                    {
 188                        var existing = await db
 189                            .WorkTags.Where(t =>
 190                                t.OwnerId == userId.Id && t.Id == input.Id
 191                            )
 192                            .Select(t => new { t.TagNamespace, t.TagName })
 193                            .FirstOrDefaultAsync(ct);
 194
 195                        if (existing is null)
 196                            return true;
 197
 198                        var tagNamespace = input.TagNamespace.HasValue
 199                            ? input.TagNamespace.Value ?? []
 1100                            : existing.TagNamespace;
 1101                        var tagName = input.TagName.HasValue
 1102                            ? input.TagName.Value
 1103                            : existing.TagName;
 1104
 1105                        return !await db.WorkTags.AnyAsync(
 1106                            other =>
 1107                                other.OwnerId == userId.Id
 1108                                && other.Id != input.Id
 1109                                && other.TagNamespace == tagNamespace
 1110                                && other.TagName == tagName,
 1111                            ct
 1112                        );
 1113                    }
 1114                )
 1115                .WithMessage("A tag with the same namespace and name already exists")
 1116                .WithErrorCode(ErrorCodes.TAG_ALREADY_EXISTS);
 1117        }
 118    }
 119
 120    public static void ApplyToTag(Models.WorkTag tag, UpdateTagInput input)
 121    {
 122        if (input.TagNamespace.HasValue)
 123            tag.TagNamespace = input.TagNamespace.Value ?? [];
 124
 125        if (input.TagName.HasValue)
 126            tag.TagName = input.TagName.Value;
 127    }
 128}
 129// Mostly Ai Generated - End