{"record":{"id":"da42844a292220bc","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-archivechannelcommandhandler","errorCode":null,"errorMessage":"no current user","messagePattern":"no current user","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Features/v1/Channels/ArchiveChannel/ArchiveChannelCommandHandler.cs","lineNumber":20,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Chat.Contracts.v1.Commands;\nusing FSH.Modules.Chat.Data;\nusing FSH.Modules.Chat.Features.v1.Internal;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Chat.Features.v1.Channels.ArchiveChannel;\n\npublic sealed class ArchiveChannelCommandHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser)\n    : ICommandHandler<ArchiveChannelCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(ArchiveChannelCommand 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\n        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == cmd.ChannelId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"Channel not found.\");\n\n        channel.RequireAdmin(userId.ToString());\n\n        // Explicit soft-delete (not db.Remove): removing the aggregate cascades Deleted onto\n        // the ChannelMember rows, which the audit interceptor does not rescue (they're FK\n        // children, not owned), so they'd be hard-deleted and lost on restore.\n        channel.Archive(userId.ToString());\n        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":2,"sourceCodeEnd":36,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Channels/ArchiveChannel/ArchiveChannelCommandHandler.cs#L2-L36","documentation":"ArchiveChannelCommandHandler throws UnauthorizedException when currentUser.GetUserId() returns Guid.Empty, i.e. no authenticated user is on the request. Archiving requires a caller both for the RequireAdmin check and audit attribution.","triggerScenarios":"Calling the ArchiveChannel endpoint without a valid JWT, with an expired token or anonymous identity, or invoking the handler in tests without a mocked ICurrentUser.","commonSituations":"Client kept calling after token expiry; missing Authorization header; integration test forgot to authenticate the HttpClient.","solutions":["Send a valid non-expired bearer token with the request","Handle 401 at the client by refreshing/re-authenticating","In tests, mock ICurrentUser.GetUserId() to return a real Guid"],"exampleFix":"// before\ncurrentUser.GetUserId().Returns(Guid.Empty);\n// after\ncurrentUser.GetUserId().Returns(Guid.NewGuid());","handlingStrategy":"validation","validationCode":"var userId = currentUser.GetUserId();\nif (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");","typeGuard":"bool isAuthenticated = currentUser.GetUserId() != Guid.Empty;","tryCatchPattern":"try { await handler.Handle(cmd, ct); }\ncatch (UnauthorizedException) { /* return 401 / re-authenticate */ }","preventionTips":["Ensure JWT attached to archive requests","Handle token expiry proactively","Stub ICurrentUser in tests"],"tags":["auth","unauthorized","chat","current-user"],"backgroundTag":"authentication-required","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"}