< Summary

Information
Class: Api.Works.Endpoints.WorkEndpoints
Assembly: Api
File(s): /home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Endpoints/WorkEndpoints.cs
Line coverage
8%
Covered lines: 6
Uncovered lines: 61
Coverable lines: 67
Total lines: 171
Line coverage: 8.9%
Branch coverage
30%
Covered branches: 8
Total branches: 26
Branch coverage: 30.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Map(...)100%88100%
AddWork()0%620%
UpdateWork()0%7280%
GetWork()0%2040%
GetWorks()0%2040%
LoadWorkGetDeps()100%210%

File(s)

/home/runner/work/ProjectRead.ing/ProjectRead.ing/Api/Works/Endpoints/WorkEndpoints.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.Works.DTOs;
 11
 12using Microsoft.AspNetCore.Http.HttpResults;
 13using Microsoft.AspNetCore.Mvc;
 14using Microsoft.EntityFrameworkCore;
 15
 16namespace Api.Works.Endpoints;
 17
 18public static class WorkEndpoints
 19{
 20    public static void Map(IEndpointRouteBuilder route)
 121    {
 122        route.MapPost("", AddWork);
 123        route.MapPut("{workId}", UpdateWork);
 124        route.MapGet("{workId}", GetWork);
 125        route.MapGet("", GetWorks);
 126    }
 27
 28    [PermissionCheckAuthorize(
 29            UserPermissionBits.WorkWrite |
 30            UserPermissionBits.AuthorRead |
 31            UserPermissionBits.WorkTagRead)]
 32    public static async Task<Results<
 33        Created<WorkGetDTO>,
 34        BadRequest>>
 35        AddWork(
 36                [FromServices] PGContext db,
 37                [FromServices] ICurrentUserId userId,
 38
 39                [FromBody] WorkAddDTO workDto
 40                )
 041    {
 42
 043        if (userId.Id is null)
 044            return TypedResults.BadRequest();
 45
 046        var work = WorkAddDTOMapper.FromWorkAddDTO(workDto);
 47
 048        await db.Works.AddAsync(work);
 49
 050        await LoadWorkGetDeps(db, work);
 51
 052        return TypedResults.Created($"/api/works/{work.Id}", WorkGetDTOMapper.ToDto(work));
 053    }
 54
 55
 56    [PermissionCheckAuthorize(
 57            UserPermissionBits.WorkWrite |
 58            UserPermissionBits.AuthorRead |
 59            UserPermissionBits.WorkTagRead)]
 60    public static async Task<Results<
 61        Ok<WorkGetDTO>,
 62        Conflict,
 63        NotFound,
 64        BadRequest>>
 65            UpdateWork(
 66                    [FromServices] PGContext db,
 67                    [FromServices] IEFTransactionDIAccessorService tx,
 68                    [FromServices] ICurrentUserId userId,
 69
 70                    [FromRoute] Guid workId,
 71                    [FromBody] WorkUpdateDTO newWork
 72                    )
 073    {
 074        await tx.BeginOrGetTransactionAsync();
 075        if (userId.Id is null)
 076            return TypedResults.BadRequest();
 77
 078        if (workId != newWork.Id)
 079            return TypedResults.BadRequest();
 80
 081        var workPre = db.Works.AsNoTracking().FirstOrDefault(w => w.OwnerId == userId.Id && w.Id == workId);
 82
 083        if (workPre is null)
 084            return TypedResults.NotFound();
 85
 086        if (workPre.RowVersion != newWork.RowVersion)
 087            return TypedResults.Conflict();
 88
 089        var updatedWork = WorkUpdateDTOMapper.FromWorkUpdateDTO(newWork);
 090        updatedWork.RowVersion++;
 091        updatedWork.MetadataAddedAt = workPre.MetadataAddedAt;
 092        updatedWork.MetadataUpdatedAt = NodaTime.SystemClock.Instance.GetCurrentInstant();
 93
 094        db.Works.Update(updatedWork);
 095        await db.SaveChangesAsync();
 96
 097        await LoadWorkGetDeps(db, updatedWork);
 98
 099        return TypedResults.Ok(WorkGetDTOMapper.ToDto(updatedWork));
 0100    }
 101
 102
 103    [PermissionCheckAuthorize(
 104            UserPermissionBits.WorkRead |
 105            UserPermissionBits.AuthorRead |
 106            UserPermissionBits.WorkTagRead)]
 107    public static async Task<Results<
 108        Ok<WorkGetDTO>,
 109        NotFound,
 110        BadRequest>>
 111            GetWork(
 112                    [FromServices] PGContext db,
 113                    [FromServices] ICurrentUserId userId,
 114
 115                    [FromRoute] Guid workId
 116                    )
 0117    {
 118
 0119        if (userId.Id is null)
 0120            return TypedResults.BadRequest();
 121
 0122        var work = await db.Works
 0123            .Include(w => w.Authors)
 0124            .Include(w => w.WorkTags)
 0125            .FirstOrDefaultAsync(w => w.OwnerId == userId.Id && w.Id == workId);
 126
 0127        return work switch
 0128        {
 0129            null => TypedResults.NotFound(),
 0130            _ => TypedResults.Ok(WorkGetDTOMapper.ToDto(work)),
 0131        };
 0132    }
 133
 134
 135    [PermissionCheckAuthorize(UserPermissionBits.WorkRead | UserPermissionBits.AuthorRead)]
 136    public static async Task<
 137    Results<
 138         Ok<WorksGetDTO>,
 139         NotFound,
 140         BadRequest>>
 141    GetWorks(
 142            [FromServices] PGContext db,
 143            [FromServices] CurrentUserId userId
 144            )
 0145    {
 0146        if (userId.Id is null)
 0147            return TypedResults.BadRequest();
 148
 0149        var works = WorkSmallDTOMapper.ProjectToDTO(db.Works.Where(w => w.OwnerId == userId.Id)).ToArray();
 150
 0151        var referencedAuthorsIds = works
 0152            .SelectMany(w => w.AuthorIds)
 0153            .Distinct().ToArray();
 0154        var referencedAuthors = AuthorGetDTOMapper.ProjectToDTO(db.Authors
 0155                .Where(a => referencedAuthorsIds
 0156                .Contains(a.Id))).ToArray();
 157
 158
 0159        return TypedResults.Ok(new WorksGetDTO
 0160        {
 0161            Works = works,
 0162            ReferencedAuthors = referencedAuthors
 0163        });
 0164    }
 165
 166    private static async Task LoadWorkGetDeps(PGContext db, Models.Work work)
 0167    {
 0168        await db.Entry(work).Collection(w => w.Authors).LoadAsync();
 0169        await db.Entry(work).Collection(w => w.WorkTags).LoadAsync();
 0170    }
 171}