Skip to content

Commit

Permalink
Use MinimalAPI for marking notifications as read (#16175)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek authored May 27, 2024
1 parent 4d10d15 commit 873b493
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
notificationManager = function () {

const removeItem = (values, value) => {
const index = values.indexOf(value);

if (index > -1) {
values.splice(index, 1);

return true;
}

return false;
}

const initialize = (readUrl, wrapperSelector) => {

if (!readUrl) {
Expand All @@ -8,6 +20,8 @@ notificationManager = function () {
return;
}

const reading = [];

var elements = document.getElementsByClassName('mark-notification-as-read');

for (let i = 0; i < elements.length; i++) {
Expand All @@ -25,6 +39,13 @@ notificationManager = function () {
return;
}

if (reading.includes(messageId)) {
// If a message is pending request, no need to send another request.
return;
}

reading.push(messageId);

fetch(readUrl, {
method: 'POST',
headers: {
Expand All @@ -40,11 +61,13 @@ notificationManager = function () {
wrapper.classList.remove('notification-is-unread');
wrapper.classList.add('notification-is-read');
wrapper.setAttribute('data-is-read', true);
removeItem(reading, messageId);
}
} else {
e.target.classList.remove('notification-is-unread');
e.target.classList.add('notification-is-read');
e.target.setAttribute('data-is-read', true);
removeItem(reading, messageId);
}
}

Expand All @@ -55,7 +78,7 @@ notificationManager = function () {
}
});
});
})
});
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using OrchardCore.Entities;
using OrchardCore.Modules;
using OrchardCore.Notifications.Indexes;
using OrchardCore.Notifications.Models;
using OrchardCore.Notifications.ViewModels;
using YesSql;

namespace OrchardCore.Notifications.Endpoints.Management;

public static class MarkAsReadEndpoints
{
public const string RouteName = "NotificationsMarkAsRead";

public static IEndpointRouteBuilder AddMarkAsReadEndpoint(this IEndpointRouteBuilder builder)
{
builder.MapPost("Notifications/MarkAsRead", HandleAsync)
.AllowAnonymous()
.WithName(RouteName)
.DisableAntiforgery();

return builder;
}

private static async Task<IResult> HandleAsync(
ReadNotificationViewModel model,
IAuthorizationService authorizationService,
IHttpContextAccessor httpContextAccessor,
YesSql.ISession session,
IClock clock)
{
if (string.IsNullOrEmpty(model?.MessageId))
{
return TypedResults.BadRequest();
}

if (!await authorizationService.AuthorizeAsync(httpContextAccessor.HttpContext.User, NotificationPermissions.ManageNotifications))
{
return TypedResults.Forbid();
}

var userId = httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);

var notification = await session.Query<Notification, NotificationIndex>(x => x.NotificationId == model.MessageId && x.UserId == userId, collection: NotificationConstants.NotificationCollection).FirstOrDefaultAsync();

if (notification == null)
{
return TypedResults.NotFound();
}

var updated = false;
var readInfo = notification.As<NotificationReadInfo>();

if (!readInfo.IsRead)
{
readInfo.ReadAtUtc = clock.UtcNow;
readInfo.IsRead = true;
notification.Put(readInfo);

updated = true;

await session.SaveAsync(notification, collection: NotificationConstants.NotificationCollection);
}

return TypedResults.Ok(new
{
messageId = model.MessageId,
updated,
});
}
}
9 changes: 9 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Notifications/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OrchardCore.Admin.Models;
Expand All @@ -9,6 +12,7 @@
using OrchardCore.Navigation.Core;
using OrchardCore.Notifications.Activities;
using OrchardCore.Notifications.Drivers;
using OrchardCore.Notifications.Endpoints.Management;
using OrchardCore.Notifications.Handlers;
using OrchardCore.Notifications.Indexes;
using OrchardCore.Notifications.Migrations;
Expand Down Expand Up @@ -66,6 +70,11 @@ public override void ConfigureServices(IServiceCollection services)
services.AddScoped<IDisplayDriver<User>, UserNotificationPreferencesPartDisplayDriver>();
services.AddScoped<IDisplayDriver<Navbar>, NotificationNavbarDisplayDriver>();
}

public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
routes.AddMarkAsReadEndpoint();
}
}

[RequireFeatures("OrchardCore.Workflows")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using OrchardCore.DisplayManagement.ModelBinding
@using OrchardCore.Notifications.Endpoints.Management

@inject IDisplayManager<Notification> NotificationDisplayDriver
@inject IUpdateModelAccessor UpdateModelAccessor
Expand Down Expand Up @@ -44,20 +45,24 @@
</div>
}
</li>
<li>
<hr class="dropdown-divider">
</li>
}
else
{
<li class="text-center">@T["You have no unread notifications."]</li>
}
<li>
<hr class="dropdown-divider">
</li>
<li>
<a class="dropdown-item fw-bold" asp-action="List" asp-controller="Admin" asp-area="OrchardCore.Notifications">@T["Notification Center"]</a>
<a class="dropdown-item fw-bold" asp-action="List" asp-controller="Admin" asp-area="@NotificationConstants.Features.Notifications">@T["Notification Center"]</a>
</li>
</ul>
</div>
</li>

<script at="Foot" asp-name="notification-manager-initializes" depends-on="notification-manager">
(function () {
notificationManager.initializeContainer('@Url.Action("Read", "Api", new { area = "OrchardCore.Notifications" })')
notificationManager.initializeContainer('@Url.RouteUrl(MarkAsReadEndpoints.RouteName, new { area = NotificationConstants.Features.Notifications }))')
})();
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
*/

notificationManager = function () {
var removeItem = function removeItem(values, value) {
var index = values.indexOf(value);
if (index > -1) {
values.splice(index, 1);
return true;
}
return false;
};
var initialize = function initialize(readUrl, wrapperSelector) {
if (!readUrl) {
console.log('No readUrl was provided.');
return;
}
var reading = [];
var elements = document.getElementsByClassName('mark-notification-as-read');
var _loop = function _loop(i) {
['click', 'mouseover'].forEach(function (evt) {
Expand All @@ -20,6 +29,11 @@ notificationManager = function () {
if (!messageId) {
return;
}
if (reading.includes(messageId)) {
// If a message is pending request, no need to send another request.
return;
}
reading.push(messageId);
fetch(readUrl, {
method: 'POST',
headers: {
Expand All @@ -38,11 +52,13 @@ notificationManager = function () {
wrapper.classList.remove('notification-is-unread');
wrapper.classList.add('notification-is-read');
wrapper.setAttribute('data-is-read', true);
removeItem(reading, messageId);
}
} else {
e.target.classList.remove('notification-is-unread');
e.target.classList.add('notification-is-read');
e.target.setAttribute('data-is-read', true);
removeItem(reading, messageId);
}
}
var targetUrl = e.target.getAttribute('data-target-url');
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@using OrchardCore.DisplayManagement.ModelBinding
@using OrchardCore.DisplayManagement
@using OrchardCore.Notifications
@using OrchardCore.Notifications.Endpoints.Management
@using OrchardCore.Notifications.ViewModels

@inject IDisplayManager<Notification> NotificationDisplayDriver
Expand Down Expand Up @@ -49,15 +50,22 @@
<hr class="dropdown-divider">
</li>
}
else
{
<li class="text-center">@T["You have no unread notifications."]</li>
}
<li>
<hr class="dropdown-divider">
</li>
<li>
<a class="dropdown-item fw-bold" asp-action="List" asp-controller="Admin" asp-area="OrchardCore.Notifications">@T["Notification Center"]</a>
<a class="dropdown-item fw-bold" asp-action="List" asp-controller="Admin" asp-area="@NotificationConstants.Features.Notifications">@T["Notification Center"]</a>
</li>
</ul>
</li>

<script at="Foot" asp-name="notification-manager-initializes" depends-on="notification-manager">
(function () {
notificationManager.initializeContainer('@Url.Action("Read", "Api", new { area = "OrchardCore.Notifications" })')
notificationManager.initializeContainer('@Url.RouteUrl(MarkAsReadEndpoints.RouteName, new { area = NotificationConstants.Features.Notifications }))')
})();
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ public class NotificationConstants
// minus 4 to allow a new integer column, for example the 'Id' column,
// minus 2 to allow a new date time, for example 'ReadAtUtc'.
public const int NotificationIndexContentLength = 705;

public class Features
{
public const string Notifications = "OrchardCore.Notifications";
}
}
4 changes: 4 additions & 0 deletions src/docs/releases/2.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ public class RegisterUserFormDisplayDriver : DisplayDriver<RegisterUserForm>
}
```

### Notifications Module

The endpoint for marking notifications as read has been updated. Previously the route was `/api/notifications/read`, and now it is `/Notifications/MarkAsRead`. This change will only affect you if you have overridden the `UserNotificationNavbar` view.

### Contents

The `IContentManager` interface was modified. The method `Task<IEnumerable<ContentItem>> GetAsync(IEnumerable<string> contentItemIds, bool latest = false)` was removed. Instead use the method that accepts `VersionOptions` by providing either `VersionOptions.Latest` or `VersionOptions.Published` will be used by default.
Expand Down

0 comments on commit 873b493

Please sign in to comment.