< Summary

Line coverage
83%
Covered lines: 223
Uncovered lines: 44
Coverable lines: 267
Total lines: 477
Line coverage: 83.5%
Branch coverage
66%
Covered branches: 16
Total branches: 24
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/obj/Debug/net10.0/generated/HotChocolate.Types.Analyzers/HotChocolate.Types.Analyzers.GraphQLServerGenerator/WorkNode.UjLSLReKVUsGE0v90fV4zw.hc.g.cs

#LineLine coverage
 1// <auto-generated/>
 2
 3#nullable enable
 4#pragma warning disable
 5
 6using System;
 7using System.Runtime.CompilerServices;
 8using HotChocolate;
 9using HotChocolate.Types;
 10using HotChocolate.Execution.Configuration;
 11using Microsoft.Extensions.DependencyInjection;
 12using HotChocolate.Internal;
 13
 14namespace Api.Works.Queries
 15{
 16    public static partial class WorkNode
 17    {
 18        internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor<global::Api.Works.Queries.Work>
 119        {
 120            var extension = descriptor.Extend();
 121            var configuration = extension.Configuration;
 122            var thisType = typeof(global::Api.Works.Queries.WorkNode);
 123            var bindingResolver = extension.Context.ParameterBindingResolver;
 124            var resolvers = new __Resolvers(bindingResolver);
 25
 126            var naming = descriptor.Extend().Context.Naming;
 27
 128            descriptor
 129                .Field(naming.GetMemberName("Authors", global::HotChocolate.Types.MemberKind.ObjectField))
 130                .ExtendWith(static (field, context) =>
 131                {
 132                    var configuration = field.Configuration;
 133                    var typeInspector = field.Context.TypeInspector;
 134                    var bindingResolver = field.Context.ParameterBindingResolver;
 135                    var naming = field.Context.Naming;
 136
 137                    configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 138                        typeInspector.GetTypeRef(typeof(global::Api.Works.Queries.Author), HotChocolate.Types.TypeContex
 139                        new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode
 140                    configuration.ResultType = typeof(global::System.Collections.Generic.IReadOnlyList<global::Api.Works
 141                    configuration.DeclaringType = context.ThisType;
 142
 143                    configuration.SetSourceGeneratorFlags();
 144
 145                    var bindingInfo = field.Context.ParameterBindingResolver;
 146                    var parameter = context.Resolvers.CreateParameterDescriptor_GetAuthorsByWorkAsync_authorIdLoader();
 147                    var parameterInfo = bindingInfo.GetBindingInfo(parameter);
 148
 149                    if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument)
 050                    {
 051                        var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentCo
 052                        {
 053                            Name = naming.GetMemberName("authorIdLoader", global::HotChocolate.Types.MemberKind.Argument
 054                            Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 055                                typeInspector.GetTypeRef(typeof(global::Api.Works.Queries.WorkDataLoaders.IAuthorIdByWor
 056                                new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.Name
 057                            RuntimeType = typeof(global::Api.Works.Queries.WorkDataLoaders.IAuthorIdByWorkIdDataLoader)
 058                        };
 159
 060                        configuration.Arguments.Add(argumentConfiguration);
 061                    }
 162
 163                    parameter = context.Resolvers.CreateParameterDescriptor_GetAuthorsByWorkAsync_authorLoader();
 164                    parameterInfo = bindingInfo.GetBindingInfo(parameter);
 165
 166                    if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument)
 067                    {
 068                        var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentCo
 069                        {
 070                            Name = naming.GetMemberName("authorLoader", global::HotChocolate.Types.MemberKind.Argument),
 071                            Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 072                                typeInspector.GetTypeRef(typeof(global::Api.Works.Queries.AuthorDataLoaders.IAuthorByIdD
 073                                new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.Name
 074                            RuntimeType = typeof(global::Api.Works.Queries.AuthorDataLoaders.IAuthorByIdDataLoader)
 075                        };
 176
 077                        configuration.Arguments.Add(argumentConfiguration);
 078                    }
 179
 180                    configuration.Member = context.ThisType.GetMethod(
 181                        "GetAuthorsByWorkAsync",
 182                        global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags,
 183                        new global::System.Type[]
 184                        {
 185                            typeof(global::Api.Works.Queries.Work),
 186                            typeof(global::Api.Works.Queries.WorkDataLoaders.IAuthorIdByWorkIdDataLoader),
 187                            typeof(global::Api.Works.Queries.AuthorDataLoaders.IAuthorByIdDataLoader),
 188                            typeof(global::System.Threading.CancellationToken)
 189                        })!;
 190
 191                    var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Contex
 192                    HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration(
 193                        field.Context,
 194                        fieldDescriptor,
 195                        configuration.Member,
 196                        new global::Api.Auth.Handlers.PermissionCheckAuthorizeAttribute((global::Api.Auth.Models.UserPer
 197
 198                    bindingResolver.ApplyConfiguration(
 199                        context.Resolvers.CreateParameterDescriptor_GetAuthorsByWorkAsync_authorIdLoader(),
 1100                        fieldDescriptor);
 1101
 1102                    bindingResolver.ApplyConfiguration(
 1103                        context.Resolvers.CreateParameterDescriptor_GetAuthorsByWorkAsync_authorLoader(),
 1104                        fieldDescriptor);
 1105
 1106                    configuration.ConfigurationsAreApplied = true;
 1107                    fieldDescriptor.CreateConfiguration();
 1108
 1109                    configuration.Resolvers = context.Resolvers.GetAuthorsByWorkAsync();
 1110                    configuration.ResultPostProcessor = global::HotChocolate.Execution.ListPostProcessor<global::Api.Wor
 1111                },
 1112                (Resolvers: resolvers, ThisType: thisType));
 113
 1114            descriptor
 1115                .Field(naming.GetMemberName("Tags", global::HotChocolate.Types.MemberKind.ObjectField))
 1116                .ExtendWith(static (field, context) =>
 1117                {
 1118                    var configuration = field.Configuration;
 1119                    var typeInspector = field.Context.TypeInspector;
 1120                    var bindingResolver = field.Context.ParameterBindingResolver;
 1121                    var naming = field.Context.Naming;
 1122
 1123                    configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 1124                        typeInspector.GetTypeRef(typeof(global::Api.Works.Queries.Tag), HotChocolate.Types.TypeContext.O
 1125                        new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode
 1126                    configuration.ResultType = typeof(global::System.Collections.Generic.IReadOnlyList<global::Api.Works
 1127                    configuration.DeclaringType = context.ThisType;
 1128
 1129                    configuration.SetSourceGeneratorFlags();
 1130
 1131                    var bindingInfo = field.Context.ParameterBindingResolver;
 1132                    var parameter = context.Resolvers.CreateParameterDescriptor_GetTagsByWorkAsync_tagIdsByWorkLoader();
 1133                    var parameterInfo = bindingInfo.GetBindingInfo(parameter);
 1134
 1135                    if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument)
 0136                    {
 0137                        var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentCo
 0138                        {
 0139                            Name = naming.GetMemberName("tagIdsByWorkLoader", global::HotChocolate.Types.MemberKind.Argu
 0140                            Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 0141                                typeInspector.GetTypeRef(typeof(global::Api.Works.Queries.WorkDataLoaders.ITagIdsByWorkI
 0142                                new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.Name
 0143                            RuntimeType = typeof(global::Api.Works.Queries.WorkDataLoaders.ITagIdsByWorkIdDataLoader)
 0144                        };
 1145
 0146                        configuration.Arguments.Add(argumentConfiguration);
 0147                    }
 1148
 1149                    parameter = context.Resolvers.CreateParameterDescriptor_GetTagsByWorkAsync_tagByIdLoader();
 1150                    parameterInfo = bindingInfo.GetBindingInfo(parameter);
 1151
 1152                    if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument)
 0153                    {
 0154                        var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentCo
 0155                        {
 0156                            Name = naming.GetMemberName("tagByIdLoader", global::HotChocolate.Types.MemberKind.Argument)
 0157                            Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 0158                                typeInspector.GetTypeRef(typeof(global::Api.Works.Queries.TagNode.ITagByIdDataLoader), H
 0159                                new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.Name
 0160                            RuntimeType = typeof(global::Api.Works.Queries.TagNode.ITagByIdDataLoader)
 0161                        };
 1162
 0163                        configuration.Arguments.Add(argumentConfiguration);
 0164                    }
 1165
 1166                    parameter = context.Resolvers.CreateParameterDescriptor_GetTagsByWorkAsync_limit();
 1167                    parameterInfo = bindingInfo.GetBindingInfo(parameter);
 1168
 1169                    if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument)
 1170                    {
 1171                        var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentCo
 1172                        {
 1173                            Name = naming.GetMemberName("limit", global::HotChocolate.Types.MemberKind.Argument),
 1174                            Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
 1175                                typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input),
 1176                                new global::HotChocolate.Language.NamedTypeNode("int")),
 1177                            RuntimeType = typeof(int?)
 1178                        };
 1179
 1180                        configuration.Arguments.Add(argumentConfiguration);
 1181                    }
 1182
 1183                    configuration.Member = context.ThisType.GetMethod(
 1184                        "GetTagsByWorkAsync",
 1185                        global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags,
 1186                        new global::System.Type[]
 1187                        {
 1188                            typeof(global::Api.Works.Queries.Work),
 1189                            typeof(global::Api.Works.Queries.WorkDataLoaders.ITagIdsByWorkIdDataLoader),
 1190                            typeof(global::Api.Works.Queries.TagNode.ITagByIdDataLoader),
 1191                            typeof(int?),
 1192                            typeof(global::System.Threading.CancellationToken)
 1193                        })!;
 1194
 1195                    var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Contex
 1196                    HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration(
 1197                        field.Context,
 1198                        fieldDescriptor,
 1199                        configuration.Member,
 1200                        new global::Api.Auth.Handlers.PermissionCheckAuthorizeAttribute((global::Api.Auth.Models.UserPer
 1201
 1202                    bindingResolver.ApplyConfiguration(
 1203                        context.Resolvers.CreateParameterDescriptor_GetTagsByWorkAsync_tagIdsByWorkLoader(),
 1204                        fieldDescriptor);
 1205
 1206                    bindingResolver.ApplyConfiguration(
 1207                        context.Resolvers.CreateParameterDescriptor_GetTagsByWorkAsync_tagByIdLoader(),
 1208                        fieldDescriptor);
 1209
 1210                    bindingResolver.ApplyConfiguration(
 1211                        context.Resolvers.CreateParameterDescriptor_GetTagsByWorkAsync_limit(),
 1212                        fieldDescriptor);
 1213
 1214                    configuration.ConfigurationsAreApplied = true;
 1215                    fieldDescriptor.CreateConfiguration();
 1216
 1217                    configuration.Resolvers = context.Resolvers.GetTagsByWorkAsync();
 1218                    configuration.ResultPostProcessor = global::HotChocolate.Execution.ListPostProcessor<global::Api.Wor
 1219                },
 1220                (Resolvers: resolvers, ThisType: thisType));
 221
 222            Configure(descriptor);
 1223        }
 224
 225        static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor<global::Api.Works.Queries.Work> d
 226
 227        private sealed class __Resolvers
 228        {
 229            private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetAuthorsByWorkAsync_authorIdLoad
 230            private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetAuthorsByWorkAsync_authorLoader
 231            private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetTagsByWorkAsync_tagIdsByWorkLoa
 232            private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetTagsByWorkAsync_tagByIdLoader;
 233            private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetTagsByWorkAsync_limit;
 234
 1235            public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver)
 1236            {
 1237                _binding_GetAuthorsByWorkAsync_authorIdLoader = bindingResolver.GetBinding(CreateParameterDescriptor_Get
 1238                _binding_GetAuthorsByWorkAsync_authorLoader = bindingResolver.GetBinding(CreateParameterDescriptor_GetAu
 1239                _binding_GetTagsByWorkAsync_tagIdsByWorkLoader = bindingResolver.GetBinding(CreateParameterDescriptor_Ge
 1240                _binding_GetTagsByWorkAsync_tagByIdLoader = bindingResolver.GetBinding(CreateParameterDescriptor_GetTags
 1241                _binding_GetTagsByWorkAsync_limit = bindingResolver.GetBinding(CreateParameterDescriptor_GetTagsByWorkAs
 1242            }
 243
 244            public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetAuthorsByWorkAsync_aut
 1245                => new HotChocolate.Internal.ParameterDescriptor(
 1246                    "authorIdLoader",
 1247                    typeof(global::Api.Works.Queries.WorkDataLoaders.IAuthorIdByWorkIdDataLoader),
 1248                    isNullable: false,
 1249                    []);
 250            public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetAuthorsByWorkAsync_aut
 1251                => new HotChocolate.Internal.ParameterDescriptor(
 1252                    "authorLoader",
 1253                    typeof(global::Api.Works.Queries.AuthorDataLoaders.IAuthorByIdDataLoader),
 1254                    isNullable: false,
 1255                    []);
 256
 257            public HotChocolate.Resolvers.FieldResolverDelegates GetAuthorsByWorkAsync()
 1258                => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetAuthorsByWorkAsync);
 259
 260            private async global::System.Threading.Tasks.ValueTask<global::System.Object?> GetAuthorsByWorkAsync(global:
 1261            {
 1262                var args0 = context.Parent<global::Api.Works.Queries.Work>();
 1263                var args1 = _binding_GetAuthorsByWorkAsync_authorIdLoader.Execute<global::Api.Works.Queries.WorkDataLoad
 1264                var args2 = _binding_GetAuthorsByWorkAsync_authorLoader.Execute<global::Api.Works.Queries.AuthorDataLoad
 1265                var args3 = context.RequestAborted;
 1266                var result = await global::Api.Works.Queries.WorkNode.GetAuthorsByWorkAsync(args0, args1, args2, args3);
 1267                return result;
 1268            }
 269
 270            public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetTagsByWorkAsync_tagIds
 1271                => new HotChocolate.Internal.ParameterDescriptor(
 1272                    "tagIdsByWorkLoader",
 1273                    typeof(global::Api.Works.Queries.WorkDataLoaders.ITagIdsByWorkIdDataLoader),
 1274                    isNullable: false,
 1275                    []);
 276            public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetTagsByWorkAsync_tagByI
 1277                => new HotChocolate.Internal.ParameterDescriptor(
 1278                    "tagByIdLoader",
 1279                    typeof(global::Api.Works.Queries.TagNode.ITagByIdDataLoader),
 1280                    isNullable: false,
 1281                    []);
 282            public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetTagsByWorkAsync_limit(
 1283                => new HotChocolate.Internal.ParameterDescriptor(
 1284                    "limit",
 1285                    typeof(int?),
 1286                    isNullable: true,
 1287                    []);
 288
 289            public HotChocolate.Resolvers.FieldResolverDelegates GetTagsByWorkAsync()
 1290                => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetTagsByWorkAsync);
 291
 292            private async global::System.Threading.Tasks.ValueTask<global::System.Object?> GetTagsByWorkAsync(global::Ho
 1293            {
 1294                var args0 = context.Parent<global::Api.Works.Queries.Work>();
 1295                var args1 = _binding_GetTagsByWorkAsync_tagIdsByWorkLoader.Execute<global::Api.Works.Queries.WorkDataLoa
 1296                var args2 = _binding_GetTagsByWorkAsync_tagByIdLoader.Execute<global::Api.Works.Queries.TagNode.ITagById
 1297                var args3 = _binding_GetTagsByWorkAsync_limit.Execute<int?>(context);
 1298                var args4 = context.RequestAborted;
 1299                var result = await global::Api.Works.Queries.WorkNode.GetTagsByWorkAsync(args0, args1, args2, args3, arg
 1300                return result;
 1301            }
 302        }
 303    }
 304}
 305

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Queries/Work.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 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    )
 154    {
 155        var ids = await authorIdLoader.LoadAsync(work.Id, ct);
 156        ids ??= [];
 157        return (await authorLoader.LoadAsync(ids, ct))!;
 158    }
 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    )
 169    {
 170        var ids = await tagIdsByWorkLoader.LoadAsync(work.Id, ct);
 171        if (limit is not null)
 172        {
 173            ids = [.. ids?.Take((int)limit) ?? []];
 174        }
 175        ids ??= [];
 176        return (await tagByIdLoader.LoadAsync(ids, ct))!;
 177    }
 78
 79    [PermissionCheckAuthorize(UserPermissionBits.WorkRead)]
 80    [GraphQLIgnore]
 81    public static async Task<Work?> GetByIdAsync(
 82        [Service] IWorkByIdDataLoader workById,
 83        Guid id,
 84        CancellationToken ct
 185    ) => 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    ) =>
 100        await db.Works.GetManyToManyIds(
 101            ids,
 102            (Guid)userId.Id!,
 103            w => w.WorkTag_Works.Select(wt => wt.WorkTagId),
 104            ct
 105        );
 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    ) =>
 117        await db.Works.GetManyToManyIds(
 118            ids,
 119            (Guid)userId.Id!,
 120            w => w.Work_Authors.Select(wa => wa.AuthorId),
 121            ct
 122        );
 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    )
 134    {
 135        return await db
 136            .Works.Where(w => w.OwnerId == userId.Id)
 137            .Where(w => ids.Contains(w.Id))
 138            .ProjectToDto()
 139            .ToDictionaryAsync(w => w.Id, cancellationToken: ct);
 140    }
 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}