< 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: 173
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 <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 Api.Database.Utils;
 10using Api.Works.DTOs;
 11using Api.Works.Query;
 12
 13using Microsoft.AspNetCore.Http.HttpResults;
 14using Microsoft.AspNetCore.Mvc;
 15using Microsoft.EntityFrameworkCore;
 16
 17namespace Api.Works.Endpoints;
 18
 19public static class WorkEndpoints
 20{
 21    public static void Map(IEndpointRouteBuilder route)
 122    {
 123        route.MapPost("", AddWork);
 124        route.MapPut("{workId}", UpdateWork);
 125        route.MapGet("{workId}", GetWork);
 126        route.MapGet("", GetWorks);
 127    }
 28
 29    [PermissionCheckAuthorize(
 30            UserPermissionBits.WorkWrite |
 31            UserPermissionBits.AuthorRead |
 32            UserPermissionBits.WorkTagRead)]
 33    public static async Task<Results<
 34        Created<WorkGetDTO>,
 35        BadRequest>>
 36        AddWork(
 37                [FromServices] PGContext db,
 38                [FromServices] ICurrentUserId userId,
 39
 40                [FromBody] WorkAddDTO workDto
 41                )
 042    {
 43
 044        if (userId.Id is null)
 045            return TypedResults.BadRequest();
 46
 047        var work = WorkAddDTOMapper.FromWorkAddDTO(workDto);
 48
 049        await db.Works.AddAsync(work);
 50
 051        await LoadWorkGetDeps(db, work);
 52
 053        return TypedResults.Created($"/api/works/{work.Id}", WorkGetDTOMapper.ToDto(work));
 054    }
 55
 56
 57    [PermissionCheckAuthorize(
 58            UserPermissionBits.WorkWrite |
 59            UserPermissionBits.AuthorRead |
 60            UserPermissionBits.WorkTagRead)]
 61    public static async Task<Results<
 62        Ok<WorkGetDTO>,
 63        Conflict,
 64        NotFound,
 65        BadRequest>>
 66            UpdateWork(
 67                    [FromServices] PGContext db,
 68                    [FromServices] IEFTransactionDIAccessorService tx,
 69                    [FromServices] ICurrentUserId userId,
 70
 71                    [FromRoute] Guid workId,
 72                    [FromBody] WorkUpdateDTO newWork
 73                    )
 074    {
 075        await tx.BeginOrGetTransactionAsync();
 076        if (userId.Id is null)
 077            return TypedResults.BadRequest();
 78
 079        if (workId != newWork.Id)
 080            return TypedResults.BadRequest();
 81
 082        var workPre = db.Works.AsNoTracking().FirstOrDefault(w => w.OwnerId == userId.Id && w.Id == workId);
 83
 084        if (workPre is null)
 085            return TypedResults.NotFound();
 86
 087        if (workPre.RowVersion != newWork.RowVersion)
 088            return TypedResults.Conflict();
 89
 090        var updatedWork = WorkUpdateDTOMapper.FromWorkUpdateDTO(newWork);
 091        updatedWork.RowVersion++;
 092        updatedWork.MetadataAddedAt = workPre.MetadataAddedAt;
 093        updatedWork.MetadataUpdatedAt = NodaTime.SystemClock.Instance.GetCurrentInstant();
 94
 095        db.Works.Update(updatedWork);
 096        await db.SaveChangesAsync();
 97
 098        await LoadWorkGetDeps(db, updatedWork);
 99
 0100        return TypedResults.Ok(WorkGetDTOMapper.ToDto(updatedWork));
 0101    }
 102
 103
 104    [PermissionCheckAuthorize(
 105            UserPermissionBits.WorkRead |
 106            UserPermissionBits.AuthorRead |
 107            UserPermissionBits.WorkTagRead)]
 108    public static async Task<Results<
 109        Ok<WorkGetDTO>,
 110        NotFound,
 111        BadRequest>>
 112            GetWork(
 113                    [FromServices] PGContext db,
 114                    [FromServices] ICurrentUserId userId,
 115
 116                    [FromRoute] Guid workId
 117                    )
 0118    {
 119
 0120        if (userId.Id is null)
 0121            return TypedResults.BadRequest();
 122
 0123        var work = await db.Works
 0124            .Include(w => w.Authors)
 0125            .Include(w => w.WorkTags)
 0126            .FirstOrDefaultAsync(w => w.OwnerId == userId.Id && w.Id == workId);
 127
 0128        return work switch
 0129        {
 0130            null => TypedResults.NotFound(),
 0131            _ => TypedResults.Ok(WorkGetDTOMapper.ToDto(work)),
 0132        };
 0133    }
 134
 135
 136    [PermissionCheckAuthorize(UserPermissionBits.WorkRead | UserPermissionBits.AuthorRead)]
 137    public static async Task<
 138    Results<
 139         Ok<WorksGetDTO>,
 140         NotFound,
 141         BadRequest>>
 142    GetWorks(
 143            [FromServices] PGContext db,
 144            [FromServices] CurrentUserId userId,
 145            [FromBody] WorkSelector query
 146            )
 0147    {
 0148        if (userId.Id is null)
 0149            return TypedResults.BadRequest();
 150
 0151        var works = WorkSmallDTOMapper.ProjectToDTO(db.Works.Where(w => w.OwnerId == userId.Id)).ToArray();
 152
 0153        var referencedAuthorsIds = works
 0154            .SelectMany(w => w.AuthorIds)
 0155            .Distinct().ToArray();
 0156        var referencedAuthors = AuthorGetDTOMapper.ProjectToDTO(db.Authors
 0157                .Where(a => referencedAuthorsIds
 0158                .Contains(a.Id))).ToArray();
 159
 160
 0161        return TypedResults.Ok(new WorksGetDTO
 0162        {
 0163            Works = works,
 0164            ReferencedAuthors = referencedAuthors
 0165        });
 0166    }
 167
 168    private static async Task LoadWorkGetDeps(PGContext db, Models.Work work)
 0169    {
 0170        await db.Entry(work).Collection(w => w.Authors).LoadAsync();
 0171        await db.Entry(work).Collection(w => w.WorkTags).LoadAsync();
 0172    }
 173}