< Summary

Information
Class: Api.Works.Mutations.AddTagInput.AddTagInputValidator
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Mutations/AddTagMutation.cs
Line coverage
100%
Covered lines: 21
Uncovered lines: 0
Coverable lines: 21
Total lines: 116
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
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()100%11100%

File(s)

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

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik <alper@alper-celik.dev>
 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.Queries;
 13using FluentValidation;
 14using Microsoft.EntityFrameworkCore;
 15using NodaTime;
 16using Npgsql;
 17using Riok.Mapperly.Abstractions;
 18
 19namespace Api.Works.Mutations;
 20
 21[MutationType]
 22public static partial class AddTagMutations
 23{
 24    [PermissionCheckAuthorize(UserPermissionBits.TagWrite)]
 25    [Error(typeof(DomainError))]
 26    public static async Task<AddTagPayload> AddTagAsync(
 27        [Service] PGContext db,
 28        [Service] ICurrentUserId userId,
 29        [Service] IEFTransactionDIAccessorService txGetter,
 30        [Service] IValidator<AddTagInput> validator,
 31        AddTagInput input,
 32        CancellationToken ct
 33    )
 34    {
 35        await validator.ValidateOrThrowInputAsync(input, ct);
 36
 37        var tx = await txGetter.BeginOrGetTransactionAsync();
 38
 39        var tag = AddTagInputMapper.CreateFromDto(input, userId.Id!.Value, Now());
 40
 41        await db.AddAsync(tag, cancellationToken: ct);
 42        await db.SaveChangesOrThrowAsync(
 43            PostgresErrorCodes.UniqueViolation,
 44            ErrorCodes.TAG_ALREADY_EXISTS,
 45            "A tag with the same namespace and name already exists",
 46            ct
 47        );
 48
 49        await tx.CommitAsync(ct);
 50        return new AddTagPayload(TagMapper.ToDto(tag));
 51    }
 52}
 53
 54public record AddTagPayload(Queries.Tag Tag);
 55
 56public record AddTagInput
 57{
 58    public required string[] TagNamespace { get; init; }
 59
 60    public required string TagName { get; init; }
 61
 62    public class AddTagInputValidator : AbstractValidator<AddTagInput>
 63    {
 164        public AddTagInputValidator(
 165            PGContext db,
 166            ICurrentUserId userId,
 167            IEFTransactionDIAccessorService tx
 168        )
 169        {
 170            RuleFor(t => t).BeginTransaction(tx);
 71
 172            RuleFor(t => t)
 173                .MustAsync(
 174                    async (input, ct) =>
 175                        !await db.WorkTags.AnyAsync(
 176                            existing =>
 177                                existing.OwnerId == userId.Id
 178                                && existing.TagNamespace == input.TagNamespace
 179                                && existing.TagName == input.TagName,
 180                            ct
 181                        )
 182                )
 183                .WithMessage("A tag with the same namespace and name already exists")
 184                .WithErrorCode(ErrorCodes.TAG_ALREADY_EXISTS);
 185        }
 86    }
 87}
 88
 89[Mapper]
 90public static partial class AddTagInputMapper
 91{
 92    [MapperIgnoreTarget(nameof(Models.WorkTag.RowVersion))]
 93    [MapperIgnoreTarget(nameof(Models.WorkTag.WorkTagWorks))]
 94    [MapperIgnoreTarget(nameof(Models.WorkTag.Works))]
 95    [MapperIgnoreTarget(nameof(Models.WorkTag.Owner))]
 96    private static partial Models.WorkTag CreateFromDtoInternal(
 97        AddTagInput w,
 98        Guid id,
 99        Instant metadataAddedAt,
 100        Instant metadataUpdatedAt
 101    );
 102
 103    public static Models.WorkTag CreateFromDto(AddTagInput w, Guid ownerId, Instant now)
 104    {
 105        var id = Guid.CreateVersion7().WithPostfix(Models.WorkTag.IdPostfix);
 106        var tag = CreateFromDtoInternal(w, id, now, now);
 107        tag.OwnerId = ownerId;
 108
 109        return tag;
 110    }
 111
 112    [UserMapping(Default = true)]
 113    public static ZonedDateTime FromInstantToZonedDateTime(Instant i) =>
 114        MapperUtils.FromInstantToZonedDateTime(i);
 115}
 116// Mostly Ai Generated - End