Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use MinimalAPI for marking notifications as read #16175

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}
}

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";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the route how it was in other code base, because it will be used once

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you are asking here. This is a route name. This is used to identify the route path later


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();
}
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}

[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;
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
};
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);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}
}
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