{"record":{"id":"eb1b44f2ed97a853","repo":"fullstackhero/dotnet-starter-kit","slug":"message-not-found-pinmessagecommandhandler","errorCode":null,"errorMessage":"Message not found.","messagePattern":"Message not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Features/v1/Messages/PinMessage/PinMessageCommandHandler.cs","lineNumber":28,"sourceCode":"\nnamespace FSH.Modules.Chat.Features.v1.Messages.PinMessage;\n\npublic sealed class PinMessageCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser,\n    IHubContext<AppHub> hub)\n    : ICommandHandler<PinMessageCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(PinMessageCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");\n        var currentUserId = userId.ToString();\n\n        var message = await db.Messages.FirstOrDefaultAsync(m => m.Id == cmd.MessageId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Message not found.\");\n\n        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == message.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Message not found.\");\n        channel.RequireMember(currentUserId);\n\n        message.Pin(currentUserId);\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n\n        await hub.Clients.Group($\"channel:{channel.Id}\")\n            .SendAsync(\"ChatMessagePinned\", message.ToDto(), cancellationToken)\n            .ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":10,"sourceCodeEnd":44,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Messages/PinMessage/PinMessageCommandHandler.cs#L10-L44","documentation":"The message lookup by cmd.MessageId found no row, so the handler throws NotFoundException('Message not found.') (HTTP 404). Thrown at the first query, when the message id does not exist or is filtered out (e.g. other tenant).","triggerScenarios":"Pinning a MessageId that does not exist, was deleted, or belongs to another tenant.","commonSituations":"Client pins from stale list state after the message was deleted, cross-tenant id, or a bad id passed by an automation script.","solutions":["Verify the message id exists in the current tenant before pinning","Refresh the message list in the client and surface 404 as 'message removed'","Check soft-delete state in the Messages table","Confirm tenant resolution matches the message's tenant"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const msg = messages.find(m => m.id === messageId);\nif (!msg) throw new Error(`message ${messageId} not in current list`);","typeGuard":"function isPinnable(m: Message | undefined): m is Message { return m !== undefined && !m.deleted; }","tryCatchPattern":"try { await pinMessage(messageId); }\ncatch (e) { if (isNotFound(e)) { removeMessageFromUi(messageId); toast('Message was removed'); return; } throw e; }","preventionTips":["Sync message list after deletes","Only pin ids from live server state","Match tenant context with the message tenant","Treat 404 as 'message gone' UI state"],"tags":["chat","not-found","message"],"backgroundTag":"entity-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}