< Summary

Line coverage
94%
Covered lines: 17
Uncovered lines: 1
Coverable lines: 18
Total lines: 137
Line coverage: 94.4%
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
File 1: CreateFromDtoInternal(...)100%11100%
File 2: CreateFromDto(...)100%11100%
File 2: FromInstantToZonedDateTime(...)100%210%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/obj/Debug/net10.0/generated/Riok.Mapperly/Riok.Mapperly.MapperGenerator/AddTagInputMapper.g.cs

#LineLine coverage
 1// <auto-generated />
 2#nullable enable
 3namespace Api.Works.Mutations
 4{
 5    public static partial class AddTagInputMapper
 6    {
 7        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.3.1.0")]
 8        private static partial global::Api.Works.Models.WorkTag CreateFromDtoInternal(global::Api.Works.Mutations.AddTag
 19        {
 110            var target = new global::Api.Works.Models.WorkTag()
 111            {
 112                TagNamespace = w.TagNamespace,
 113                TagName = w.TagName,
 114            };
 115            target.Id = id;
 116            target.MetadataAddedAt = metadataAddedAt;
 117            target.MetadataUpdatedAt = metadataUpdatedAt;
 118            return target;
 119        }
 20    }
 21}

/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    {
 64        public AddTagInputValidator(
 65            PGContext db,
 66            ICurrentUserId userId,
 67            IEFTransactionDIAccessorService tx
 68        )
 69        {
 70            RuleFor(t => t).BeginTransaction(tx);
 71
 72            RuleFor(t => t)
 73                .MustAsync(
 74                    async (input, ct) =>
 75                        !await db.WorkTags.AnyAsync(
 76                            existing =>
 77                                existing.OwnerId == userId.Id
 78                                && existing.TagNamespace == input.TagNamespace
 79                                && existing.TagName == input.TagName,
 80                            ct
 81                        )
 82                )
 83                .WithMessage("A tag with the same namespace and name already exists")
 84                .WithErrorCode(ErrorCodes.TAG_ALREADY_EXISTS);
 85        }
 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)
 1104    {
 1105        var id = Guid.CreateVersion7().WithPostfix(Models.WorkTag.IdPostfix);
 1106        var tag = CreateFromDtoInternal(w, id, now, now);
 1107        tag.OwnerId = ownerId;
 108
 1109        return tag;
 1110    }
 111
 112    [UserMapping(Default = true)]
 113    public static ZonedDateTime FromInstantToZonedDateTime(Instant i) =>
 0114        MapperUtils.FromInstantToZonedDateTime(i);
 115}
 116// Mostly Ai Generated - End