| | | 1 | | // SPDX-FileCopyrightText: 2026 Alper Çelik <[email protected]> |
| | | 2 | | // |
| | | 3 | | // SPDX-License-Identifier: AGPL-3.0-or-later |
| | | 4 | | |
| | | 5 | | using Api.Auth.Handlers; |
| | | 6 | | using Api.Auth.Models; |
| | | 7 | | using Api.Auth.Utils; |
| | | 8 | | using Api.Database; |
| | | 9 | | using Api.Database.Utils; |
| | | 10 | | using Api.Works.DTOs; |
| | | 11 | | |
| | | 12 | | using Microsoft.AspNetCore.Http.HttpResults; |
| | | 13 | | using Microsoft.AspNetCore.Mvc; |
| | | 14 | | using Microsoft.EntityFrameworkCore; |
| | | 15 | | |
| | | 16 | | namespace Api.Works.Endpoints; |
| | | 17 | | |
| | | 18 | | public static class WorkEndpoints |
| | | 19 | | { |
| | | 20 | | public static void Map(IEndpointRouteBuilder route) |
| | 1 | 21 | | { |
| | 1 | 22 | | route.MapPost("", AddWork); |
| | 1 | 23 | | route.MapPut("{workId}", UpdateWork); |
| | 1 | 24 | | route.MapGet("{workId}", GetWork); |
| | 1 | 25 | | route.MapGet("", GetWorks); |
| | 1 | 26 | | } |
| | | 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 | | ) |
| | 0 | 41 | | { |
| | | 42 | | |
| | 0 | 43 | | if (userId.Id is null) |
| | 0 | 44 | | return TypedResults.BadRequest(); |
| | | 45 | | |
| | 0 | 46 | | var work = WorkAddDTOMapper.FromWorkAddDTO(workDto); |
| | | 47 | | |
| | 0 | 48 | | await db.Works.AddAsync(work); |
| | | 49 | | |
| | 0 | 50 | | await LoadWorkGetDeps(db, work); |
| | | 51 | | |
| | 0 | 52 | | return TypedResults.Created($"/api/works/{work.Id}", WorkGetDTOMapper.ToDto(work)); |
| | 0 | 53 | | } |
| | | 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 | | ) |
| | 0 | 73 | | { |
| | 0 | 74 | | await tx.BeginOrGetTransactionAsync(); |
| | 0 | 75 | | if (userId.Id is null) |
| | 0 | 76 | | return TypedResults.BadRequest(); |
| | | 77 | | |
| | 0 | 78 | | if (workId != newWork.Id) |
| | 0 | 79 | | return TypedResults.BadRequest(); |
| | | 80 | | |
| | 0 | 81 | | var workPre = db.Works.AsNoTracking().FirstOrDefault(w => w.OwnerId == userId.Id && w.Id == workId); |
| | | 82 | | |
| | 0 | 83 | | if (workPre is null) |
| | 0 | 84 | | return TypedResults.NotFound(); |
| | | 85 | | |
| | 0 | 86 | | if (workPre.RowVersion != newWork.RowVersion) |
| | 0 | 87 | | return TypedResults.Conflict(); |
| | | 88 | | |
| | 0 | 89 | | var updatedWork = WorkUpdateDTOMapper.FromWorkUpdateDTO(newWork); |
| | 0 | 90 | | updatedWork.RowVersion++; |
| | 0 | 91 | | updatedWork.MetadataAddedAt = workPre.MetadataAddedAt; |
| | 0 | 92 | | updatedWork.MetadataUpdatedAt = NodaTime.SystemClock.Instance.GetCurrentInstant(); |
| | | 93 | | |
| | 0 | 94 | | db.Works.Update(updatedWork); |
| | 0 | 95 | | await db.SaveChangesAsync(); |
| | | 96 | | |
| | 0 | 97 | | await LoadWorkGetDeps(db, updatedWork); |
| | | 98 | | |
| | 0 | 99 | | return TypedResults.Ok(WorkGetDTOMapper.ToDto(updatedWork)); |
| | 0 | 100 | | } |
| | | 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 | | ) |
| | 0 | 117 | | { |
| | | 118 | | |
| | 0 | 119 | | if (userId.Id is null) |
| | 0 | 120 | | return TypedResults.BadRequest(); |
| | | 121 | | |
| | 0 | 122 | | var work = await db.Works |
| | 0 | 123 | | .Include(w => w.Authors) |
| | 0 | 124 | | .Include(w => w.WorkTags) |
| | 0 | 125 | | .FirstOrDefaultAsync(w => w.OwnerId == userId.Id && w.Id == workId); |
| | | 126 | | |
| | 0 | 127 | | return work switch |
| | 0 | 128 | | { |
| | 0 | 129 | | null => TypedResults.NotFound(), |
| | 0 | 130 | | _ => TypedResults.Ok(WorkGetDTOMapper.ToDto(work)), |
| | 0 | 131 | | }; |
| | 0 | 132 | | } |
| | | 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 | | ) |
| | 0 | 145 | | { |
| | 0 | 146 | | if (userId.Id is null) |
| | 0 | 147 | | return TypedResults.BadRequest(); |
| | | 148 | | |
| | 0 | 149 | | var works = WorkSmallDTOMapper.ProjectToDTO(db.Works.Where(w => w.OwnerId == userId.Id)).ToArray(); |
| | | 150 | | |
| | 0 | 151 | | var referencedAuthorsIds = works |
| | 0 | 152 | | .SelectMany(w => w.AuthorIds) |
| | 0 | 153 | | .Distinct().ToArray(); |
| | 0 | 154 | | var referencedAuthors = AuthorGetDTOMapper.ProjectToDTO(db.Authors |
| | 0 | 155 | | .Where(a => referencedAuthorsIds |
| | 0 | 156 | | .Contains(a.Id))).ToArray(); |
| | | 157 | | |
| | | 158 | | |
| | 0 | 159 | | return TypedResults.Ok(new WorksGetDTO |
| | 0 | 160 | | { |
| | 0 | 161 | | Works = works, |
| | 0 | 162 | | ReferencedAuthors = referencedAuthors |
| | 0 | 163 | | }); |
| | 0 | 164 | | } |
| | | 165 | | |
| | | 166 | | private static async Task LoadWorkGetDeps(PGContext db, Models.Work work) |
| | 0 | 167 | | { |
| | 0 | 168 | | await db.Entry(work).Collection(w => w.Authors).LoadAsync(); |
| | 0 | 169 | | await db.Entry(work).Collection(w => w.WorkTags).LoadAsync(); |
| | 0 | 170 | | } |
| | | 171 | | } |