< Summary

Information
Class: Api.Works.Queries.WorkDataLoaders
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Queries/Work.cs
Line coverage
100%
Covered lines: 19
Uncovered lines: 0
Coverable lines: 19
Total lines: 172
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetTagIdsByWorkIdAsync()100%11100%
GetAuthorIdByWorkIdAsync()100%11100%
GetWorkByIdAsync()100%22100%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Queries/Work.cs

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik <alper@alper-celik.dev>
 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 GreenDonut.Data;
 10using HotChocolate.Types.Pagination;
 11using Microsoft.EntityFrameworkCore;
 12using Riok.Mapperly.Abstractions;
 13using static Api.Works.Queries.AuthorDataLoaders;
 14using static Api.Works.Queries.WorkDataLoaders;
 15
 16namespace Api.Works.Queries;
 17
 18[QueryType]
 19public static partial class WorkQuery
 20{
 21    [PermissionCheckAuthorize(UserPermissionBits.WorkRead)]
 22    [UseFiltering]
 23    [UseSorting]
 24    public static async Task<PageConnection<Work>> GetWorks(
 25        [Service] PGContext db,
 26        [Service] ICurrentUserId userId,
 27        [Service] IWorkByIdDataLoader workById,
 28        QueryContext<Work> qc,
 29        PagingArguments pagingArguments,
 30        CancellationToken ct
 31    )
 32    {
 33        return await db
 34            .Works.Where(w => w.OwnerId == userId.Id)
 35            .ProjectToDto()
 36            .WithQueryContext(qc)
 37            .ToPageWithDataLoaderAsync(pagingArguments, workById, ct);
 38    }
 39}
 40
 41[ObjectType<Work>]
 42public static partial class WorkNode
 43{
 44    [PermissionCheckAuthorize(
 45        UserPermissionBits.AuthorRead | UserPermissionBits.WorkRead
 46    )]
 47    [GraphQLName("Authors")]
 48    public static async Task<IReadOnlyList<Author>> GetAuthorsByWorkAsync(
 49        [Parent] Work work,
 50        IAuthorIdByWorkIdDataLoader authorIdLoader,
 51        IAuthorByIdDataLoader authorLoader,
 52        CancellationToken ct
 53    )
 54    {
 55        var ids = await authorIdLoader.LoadAsync(work.Id, ct);
 56        ids ??= [];
 57        return (await authorLoader.LoadAsync(ids, ct))!;
 58    }
 59
 60    [PermissionCheckAuthorize(UserPermissionBits.TagRead | UserPermissionBits.WorkRead)]
 61    [GraphQLName("Tags")]
 62    public static async Task<IReadOnlyList<Tag>> GetTagsByWorkAsync(
 63        [Parent] Work work,
 64        ITagIdsByWorkIdDataLoader tagIdsByWorkLoader,
 65        TagNode.ITagByIdDataLoader tagByIdLoader,
 66        int? limit,
 67        CancellationToken ct
 68    )
 69    {
 70        var ids = await tagIdsByWorkLoader.LoadAsync(work.Id, ct);
 71        if (limit is not null)
 72        {
 73            ids = [.. ids?.Take((int)limit) ?? []];
 74        }
 75        ids ??= [];
 76        return (await tagByIdLoader.LoadAsync(ids, ct))!;
 77    }
 78
 79    [PermissionCheckAuthorize(UserPermissionBits.WorkRead)]
 80    [GraphQLIgnore]
 81    public static async Task<Work?> GetByIdAsync(
 82        [Service] IWorkByIdDataLoader workById,
 83        Guid id,
 84        CancellationToken ct
 85    ) => await workById.LoadAsync(id, cancellationToken: ct);
 86}
 87
 88public static class WorkDataLoaders
 89{
 90    public interface ITagIdsByWorkIdDataLoader : IBatchDataLoader<Guid, Guid[]>;
 91
 92    [GraphQLIgnore]
 93    [DataLoader<ITagIdsByWorkIdDataLoader>]
 94    public static async Task<IDictionary<Guid, Guid[]>> GetTagIdsByWorkIdAsync(
 95        IReadOnlyList<Guid> ids,
 96        [Service] PGContext db,
 97        [Service] ICurrentUserId userId,
 98        CancellationToken ct
 99    ) =>
 1100        await db.Works.GetManyToManyIds(
 1101            ids,
 1102            (Guid)userId.Id!,
 1103            w => w.WorkTag_Works.Select(wt => wt.WorkTagId),
 1104            ct
 1105        );
 106
 107    public interface IAuthorIdByWorkIdDataLoader : IBatchDataLoader<Guid, Guid[]>;
 108
 109    [GraphQLIgnore]
 110    [DataLoader<IAuthorIdByWorkIdDataLoader>]
 111    public static async Task<IDictionary<Guid, Guid[]>> GetAuthorIdByWorkIdAsync(
 112        IReadOnlyList<Guid> ids,
 113        [Service] PGContext db,
 114        [Service] ICurrentUserId userId,
 115        CancellationToken ct
 116    ) =>
 1117        await db.Works.GetManyToManyIds(
 1118            ids,
 1119            (Guid)userId.Id!,
 1120            w => w.Work_Authors.Select(wa => wa.AuthorId),
 1121            ct
 1122        );
 123
 124    public interface IWorkByIdDataLoader : IBatchDataLoader<Guid, Work>;
 125
 126    [GraphQLIgnore]
 127    [DataLoader<IWorkByIdDataLoader>]
 128    public static async Task<IDictionary<Guid, Work>> GetWorkByIdAsync(
 129        IReadOnlyList<Guid> ids,
 130        [Service] PGContext db,
 131        [Service] ICurrentUserId userId,
 132        CancellationToken ct
 133    )
 1134    {
 1135        return await db
 1136            .Works.Where(w => w.OwnerId == userId.Id)
 1137            .Where(w => ids.Contains(w.Id))
 1138            .ProjectToDto()
 1139            .ToDictionaryAsync(w => w.Id, cancellationToken: ct);
 1140    }
 141}
 142
 143[Node(NodeResolverType = typeof(WorkNode), NodeResolver = nameof(WorkNode.GetByIdAsync))]
 144public class Work : IEntityMetadata, INode
 145{
 146    public static byte IdPostfix => Models.Work.IdPostfix;
 147
 148    public Guid Id { get; set; }
 149    public int RowVersion { get; set; }
 150    public NodaTime.Instant MetadataAddedAt { get; set; }
 151    public NodaTime.Instant MetadataUpdatedAt { get; set; }
 152
 153    public required string Title { get; set; }
 154
 155    public string? Description { get; set; }
 156
 157    public NodaTime.Instant? WorkPublishedAt { get; set; }
 158    public NodaTime.Instant? WorkUpdatedAt { get; set; }
 159    public List<WorkIdentifier> WorkIdentifiers { get; set; } = [];
 160}
 161
 162public record WorkIdentifier(string WorkIdentifierType, string WorkIdentifierValue);
 163
 164[Mapper]
 165public static partial class WorkMapper
 166{
 167    public static partial Models.WorkIdentifier FromWorkIdDto(WorkIdentifier w);
 168
 169    public static partial Work? ToDto(Models.Work? w);
 170
 171    public static partial IQueryable<Work> ProjectToDto(this IQueryable<Models.Work> q);
 172}