< Summary

Information
Class: Api.Works.Mutations.UpdateWorkInput
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Mutations/UpdateWorkMutation.cs
Line coverage
97%
Covered lines: 44
Uncovered lines: 1
Coverable lines: 45
Total lines: 128
Line coverage: 97.7%
Branch coverage
80%
Covered branches: 32
Total branches: 40
Branch coverage: 80%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_IdPostfix()100%210%
.ctor(...)100%88100%
ApplyToWork(...)75%3232100%

File(s)

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

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik <[email protected]>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5using Api.Auth.Handlers;
 6using Api.Auth.Models;
 7using Api.Auth.Utils;
 8using Api.Database;
 9using Api.Database.Utils;
 10using Api.Utils;
 11using Api.Works.Models;
 12using Api.Works.Queries;
 13using FluentValidation;
 14using Microsoft.EntityFrameworkCore;
 15using NodaTime;
 16
 17namespace Api.Works.Mutations;
 18
 19[MutationType]
 20public static partial class UpdateWorkMutations
 21{
 22    [PermissionCheckAuthorize(UserPermissionBits.WorkRead | UserPermissionBits.WorkWrite)]
 23    [Error(typeof(DomainError))]
 24    public static async Task<UpdateWorkPayload> UpdateWorkAsync(
 25        [Service] PGContext db,
 26        [Service] IEFTransactionDIAccessorService txGetter,
 27        [Service] IValidator<UpdateWorkInput> validator,
 28        UpdateWorkInput input,
 29        CancellationToken ct
 30    )
 31    {
 32        await validator.ValidateOrThrowInputAsync(input, ct);
 33
 34        var tx = await txGetter.BeginOrGetTransactionAsync();
 35        var work = await db
 36            .Works.Include(w => w.WorkTag_Works)
 37            .Include(w => w.Work_Authors)
 38            .SingleAsync(w => w.Id == input.Id, cancellationToken: ct);
 39
 40        UpdateWorkInput.ApplyToWork(work, input);
 41        work.RowVersion += 1;
 42        work.MetadataUpdatedAt = Now();
 43
 44        await db.SaveChangesAsync(cancellationToken: ct);
 45
 46        await tx.CommitAsync(ct);
 47        return new UpdateWorkPayload(WorkMapper.ToDto(work));
 48    }
 49}
 50
 51public record UpdateWorkPayload(Queries.Work Work);
 52
 53public record UpdateWorkInput : IBasicEntityMetadata
 54{
 055    public static byte IdPostfix => Models.Work.IdPostfix;
 56
 57    public Guid Id { get; init; }
 58    public int RowVersion { get; init; }
 59
 60    public Optional<string> Title { get; init; }
 61    public Optional<string?> Description { get; init; }
 62
 63    public Optional<Instant?> WorkPublishedAt { get; init; }
 64    public Optional<Instant?> WorkUpdatedAt { get; init; }
 65    public Optional<List<Queries.WorkIdentifier>?> WorkIdentifiers { get; init; }
 66    public required Optional<List<Guid>?> TagIds { get; init; }
 67    public required Optional<List<Guid>?> AuthorIds { get; init; }
 68
 69    public class UpdateWorkInputValidator : AbstractValidator<UpdateWorkInput>
 70    {
 171        public UpdateWorkInputValidator(
 172            PGContext db,
 173            ICurrentUserId userId,
 174            IEFTransactionDIAccessorService tx
 175        )
 176        {
 177            RuleFor(w => w).BeginTransaction(tx);
 78
 179            RuleFor(w => w.Id).IdMustExist(db.Works, userId.Id);
 80
 181            RuleFor(w => w.RowVersion).RowVersionMustMatch(db.Works);
 82
 183            RuleFor(w => w.TagIds.Value!)
 184                .MustBeDistinct(nameof(TagIds))
 185                .WhenOptionalSet(w => w.TagIds);
 186            RuleFor(w => w.TagIds.Value!)
 187                .IdsMustExist(db.WorkTags, userId.Id)
 188                .WhenOptionalSet(w => w.TagIds);
 89
 190            RuleFor(w => w.AuthorIds.Value!)
 191                .MustBeDistinct(nameof(AuthorIds))
 192                .WhenOptionalSet(w => w.AuthorIds);
 193            RuleFor(w => w.AuthorIds.Value!)
 194                .IdsMustExist(db.Authors, userId.Id)
 195                .WhenOptionalSet(w => w.AuthorIds);
 196        }
 97    }
 98
 99    public static void ApplyToWork(Models.Work work, UpdateWorkInput input)
 1100    {
 1101        if (input.Title.HasValue)
 1102            work.Title = input.Title.Value;
 103
 1104        if (input.Description.HasValue)
 1105            work.Description = input.Description.Value;
 106
 1107        if (input.WorkPublishedAt.HasValue)
 1108            work.WorkPublishedAt = input.WorkPublishedAt.Value?.InUtc();
 109
 1110        if (input.WorkUpdatedAt.HasValue)
 1111            work.WorkUpdatedAt = input.WorkUpdatedAt.Value?.InUtc();
 112
 1113        if (input.WorkIdentifiers.HasValue)
 1114            work.WorkIdentifiers =
 1115                input.WorkIdentifiers.Value?.Select(WorkMapper.FromWorkIdDto).ToList()
 1116                ?? [];
 117
 1118        if (input.TagIds.HasValue)
 1119            work.WorkTag_Works =
 1120                input.TagIds.Value?.Select(t => new WorkTag_Work(work.Id, t)).ToList()
 1121                ?? [];
 122
 1123        if (input.AuthorIds.HasValue)
 1124            work.Work_Authors =
 1125                input.AuthorIds.Value?.Select(t => new Work_Author(work.Id, t)).ToList()
 1126                ?? [];
 1127    }
 128}