Перевести «не найдено» карточек на NotFoundException

CardsService (get/move/links/reminders) и CardMover бросают NotFoundException вместо null/CardResultDto(null,null)/Exists=false. CardMoveResultDto — только Error; эндпоинты без локальных 404-проверок.
This commit is contained in:
2026-09-13 18:23:53 +03:00
parent a9bfecf9cc
commit d2d6b81aa0
12 changed files with 107 additions and 152 deletions
@@ -27,9 +27,16 @@ public sealed partial class CardsService
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <returns>Карточка или null — строки нет (эндпоинт отвечает 404 «Карточка не найдена»).</returns>
public Task<CardDto?> GetCardAsync(string cardId, CancellationToken ct)
/// <summary>
/// Одна карточка по id.
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <returns>Карточка.</returns>
/// <exception cref="NotFoundException">Карточка не найдена.</exception>
public async Task<CardDto> GetCardAsync(string cardId, CancellationToken ct)
{
return _store.GetCardAsync(cardId, ct);
return await _store.GetCardAsync(cardId, ct)
?? throw new NotFoundException(CardEntityName, cardId);
}
@@ -57,7 +64,7 @@ public sealed partial class CardsService
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
{
return new CardResultDto(null, null);
throw new NotFoundException(CardEntityName, cardId);
}
if (card.Col == CardIds.Archive || card.Col == CardIds.Trash)
@@ -81,7 +88,10 @@ public sealed partial class CardsService
await _mlClient.PushAsync(text, toCol, PushWeightUser, ct);
}
return new CardResultDto(null, await _store.GetCardAsync(cardId, ct));
return new CardResultDto(
null,
await _store.GetCardAsync(cardId, ct)
?? throw new NotFoundException(CardEntityName, cardId));
}
/// <summary>
@@ -1,5 +1,6 @@
using Deal.Modules.Kanban.Application.Models;
using Deal.Modules.Settings.Application.Models;
using Deal.SharedKernel.Errors;
namespace Deal.Modules.Kanban.Application.Services;
@@ -22,17 +23,15 @@ public sealed partial class CardsService
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <param name="atMs">Время напоминания, epoch-ms.</param>
/// <returns>Результат: Error <see cref="RemindersDisabledDetail"/> (400) | Card=null без Error (404) | Card — карточка с напоминанием.</returns>
/// <returns>Результат: Error <see cref="RemindersDisabledDetail"/> (400) | Card — карточка с напоминанием.</returns>
/// <exception cref="NotFoundException">Карточка не найдена.</exception>
public async Task<CardResultDto> SetReminderAsync(
string cardId,
long atMs,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
{
return new CardResultDto(null, null);
}
CardDto card = await _store.GetCardAsync(cardId, ct)
?? throw new NotFoundException(CardEntityName, cardId);
if (!await ReadRemindersEnabledAsync(ct))
{
@@ -41,7 +40,7 @@ public sealed partial class CardsService
await _store.SetReminderAsync(cardId, atMs, ct);
CardDto saved = await _store.GetCardAsync(cardId, ct)
?? throw new InvalidOperationException("Карточка не прочиталась после установки напоминания: " + cardId);
?? throw new NotFoundException(CardEntityName, cardId);
return new CardResultDto(null, saved);
}
@@ -49,35 +48,27 @@ public sealed partial class CardsService
/// Снимает напоминание карточки.
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <returns>True — карточка есть и напоминание снято; false — карточки нет (404).</returns>
public async Task<bool> ClearReminderAsync(string cardId, CancellationToken ct)
/// <exception cref="NotFoundException">Карточка не найдена.</exception>
public async Task ClearReminderAsync(string cardId, CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
{
return false;
}
_ = await _store.GetCardAsync(cardId, ct)
?? throw new NotFoundException(CardEntityName, cardId);
await _store.ClearReminderAsync(cardId, ct);
return true;
}
/// <summary>
/// «Напомнить позже»
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <returns>True — карточка есть и напоминание отложено; false — карточки нет (404).</returns>
public async Task<bool> SnoozeReminderAsync(string cardId, CancellationToken ct)
/// <exception cref="NotFoundException">Карточка не найдена.</exception>
public async Task SnoozeReminderAsync(string cardId, CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
{
return false;
}
_ = await _store.GetCardAsync(cardId, ct)
?? throw new NotFoundException(CardEntityName, cardId);
long snoozedAtMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + ReminderSnoozeMs;
await _store.SetReminderAsync(cardId, snoozedAtMs, ct);
return true;
}
/// <summary>
@@ -12,6 +12,9 @@ namespace Deal.Modules.Kanban.Application.Services;
/// </summary>
public sealed partial class CardsService
{
// Имя сущности для текста ошибки «не найдено».
private const string CardLinkEntityName = "Ссылка карточки";
/// <summary>
/// 400 перенос по стадии
/// </summary>
@@ -159,18 +162,16 @@ public sealed partial class CardsService
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <param name="name">Название ссылки; пустое после Trim → name = url.</param>
/// <param name="url">URL ссылки (без схемы — добавится https://).</param>
/// <returns>Результат: Error (400 «Пустая ссылка») | Card=null без Error (404) | Card — карточка со ссылкой.</returns>
/// <returns>Результат: Error (400 «Пустая ссылка») | Card — карточка со ссылкой.</returns>
/// <exception cref="NotFoundException">Карточка не найдена.</exception>
public async Task<CardResultDto> AddLinkAsync(
string cardId,
string name,
string url,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
{
return new CardResultDto(null, null);
}
CardDto card = await _store.GetCardAsync(cardId, ct)
?? throw new NotFoundException(CardEntityName, cardId);
string normalizedUrl = (url ?? string.Empty).Trim();
if (normalizedUrl.Length == 0)
@@ -191,11 +192,11 @@ public sealed partial class CardsService
normalizedUrl);
if (!await _store.AddLinkAsync(cardId, link, ct))
{
return new CardResultDto(null, null);
throw new NotFoundException(CardEntityName, cardId);
}
CardDto saved = await _store.GetCardAsync(cardId, ct)
?? throw new InvalidOperationException("Карточка не прочиталась после добавления ссылки: " + cardId);
?? throw new NotFoundException(CardEntityName, cardId);
return new CardResultDto(null, saved);
}
@@ -204,7 +205,8 @@ public sealed partial class CardsService
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <param name="linkId">Id удаляемой ссылки (<c>pl_...</c>).</param>
/// <returns>Результат: Card=null без Error (404) | Card — карточка без ссылки.</returns>
/// <returns>Карточка без ссылки.</returns>
/// <exception cref="NotFoundException">Карточка или ссылка не найдены.</exception>
public async Task<CardResultDto> RemoveLinkAsync(
string cardId,
string linkId,
@@ -212,11 +214,11 @@ public sealed partial class CardsService
{
if (!await _store.RemoveLinkAsync(cardId, linkId, ct))
{
return new CardResultDto(null, null);
throw new NotFoundException(CardLinkEntityName, linkId);
}
CardDto saved = await _store.GetCardAsync(cardId, ct)
?? throw new InvalidOperationException("Карточка не прочиталась после удаления ссылки: " + cardId);
?? throw new NotFoundException(CardEntityName, cardId);
return new CardResultDto(null, saved);
}
@@ -225,7 +227,8 @@ public sealed partial class CardsService
/// </summary>
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <param name="containerId">Новый контейнер-стадия — id каталога <see cref="CardsDefaultContainers"/>.</param>
/// <returns>Результат: Error «Неизвестная стадия» (400) | Card=null без Error (карточки нет, 404) | Card — карточка после переноса.</returns>
/// <returns>Результат: Error «Неизвестная стадия» (400) | Card — карточка после переноса.</returns>
/// <exception cref="NotFoundException">Карточка не найдена.</exception>
public async Task<CardResultDto> MoveStageCardAsync(
string cardId,
string containerId,
@@ -241,11 +244,11 @@ public sealed partial class CardsService
bool moved = await _store.MoveCardStageAsync(cardId, containerId, entry, nowMs, ct);
if (!moved)
{
return new CardResultDto(null, null);
throw new NotFoundException(CardEntityName, cardId);
}
CardDto card = await _store.GetCardAsync(cardId, ct)
?? throw new InvalidOperationException("Карточка не прочиталась после move: " + cardId);
?? throw new NotFoundException(CardEntityName, cardId);
return new CardResultDto(null, card);
}