Add notifications API for PostIt home page
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
7ee0c30500
commit
8b4b6a7c53
5 changed files with 158 additions and 0 deletions
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* [Yavsc.Api] Nouveau point d'entrée `GET /api/v1/notifications/me`
|
||||||
|
pour exposer les notifications publiques et ciblées à PostIt sur la
|
||||||
|
Home page ; les notifications d'administration restent filtrées.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ namespace Yavsc
|
||||||
public const string ResourceUsagePath = "resource-usage";
|
public const string ResourceUsagePath = "resource-usage";
|
||||||
public const string ResourceUsageCurrentUserRoute = "me";
|
public const string ResourceUsageCurrentUserRoute = "me";
|
||||||
public const string ResourceUsageAdminRoute = "admin";
|
public const string ResourceUsageAdminRoute = "admin";
|
||||||
|
public const string NotificationsPath = "notifications";
|
||||||
|
public const string NotificationsCurrentUserRoute = "me";
|
||||||
public const string ResourceUsageDefaultCurrency = "EUR";
|
public const string ResourceUsageDefaultCurrency = "EUR";
|
||||||
public const string ResourceUsageCurrentUserDisplayName = "current-user";
|
public const string ResourceUsageCurrentUserDisplayName = "current-user";
|
||||||
public const string ResourceUsageAdminDisplayName = "administrator";
|
public const string ResourceUsageAdminDisplayName = "administrator";
|
||||||
|
|
|
||||||
58
src/Yavsc.Api.Client/NotificationsApiClient.cs
Normal file
58
src/Yavsc.Api.Client/NotificationsApiClient.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Yavsc.Abstract.Models.Messaging;
|
||||||
|
|
||||||
|
namespace Yavsc.Api.Client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thin HTTP client for the notifications exposed to PostIt on the home page.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class NotificationsApiClient
|
||||||
|
{
|
||||||
|
private const string PathPrefix = Constants.NotificationsPath;
|
||||||
|
|
||||||
|
private readonly IYavscApiClient _api;
|
||||||
|
private readonly Func<string> _businessBaseAddress;
|
||||||
|
|
||||||
|
public NotificationsApiClient(IYavscApiClient api, string businessBaseAddress)
|
||||||
|
: this(api, () => businessBaseAddress)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationsApiClient(IYavscApiClient api, Func<string> businessBaseAddress)
|
||||||
|
{
|
||||||
|
_api = api ?? throw new ArgumentNullException(nameof(api));
|
||||||
|
_businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress));
|
||||||
|
_ = ResolveBusinessBaseAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<Notification>> GetCurrentAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return _api.CallAsync<List<Notification>>(
|
||||||
|
HttpMethod.Get,
|
||||||
|
Absolute($"{PathPrefix}/{Constants.NotificationsCurrentUserRoute}"),
|
||||||
|
ct: ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<Notification>> GetAllAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return _api.CallAsync<List<Notification>>(
|
||||||
|
HttpMethod.Get,
|
||||||
|
Absolute(PathPrefix),
|
||||||
|
ct: ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString();
|
||||||
|
|
||||||
|
private Uri ResolveBusinessBaseAddress()
|
||||||
|
{
|
||||||
|
var raw = _businessBaseAddress();
|
||||||
|
if (string.IsNullOrWhiteSpace(raw))
|
||||||
|
throw new InvalidOperationException("Business base address is required.");
|
||||||
|
|
||||||
|
return new Uri(raw, UriKind.Absolute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Yavsc.Abstract.Models.Messaging;
|
||||||
|
using Yavsc.Models;
|
||||||
|
using Yavsc.Server.Helpers;
|
||||||
|
|
||||||
|
namespace Yavsc.ApiControllers.Administration;
|
||||||
|
|
||||||
|
[Produces("application/json")]
|
||||||
|
[Route(Constants.APIPrefix + "/" + Constants.NotificationsPath)]
|
||||||
|
public sealed class NotificationsApiController : Controller
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
|
||||||
|
public NotificationsApiController(ApplicationDbContext dbContext)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[HttpGet(Constants.NotificationsCurrentUserRoute)]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<List<Notification>>> GetCurrentUserNotifications()
|
||||||
|
{
|
||||||
|
var currentUserId = User.GetUserId();
|
||||||
|
var isAdmin = User.IsInRole(Constants.AdminGroupName);
|
||||||
|
|
||||||
|
var dismissedIds = string.IsNullOrWhiteSpace(currentUserId)
|
||||||
|
? new HashSet<long>()
|
||||||
|
: await _dbContext.DismissClicked
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(d => d.UserId == currentUserId)
|
||||||
|
.Select(d => d.NotificationId)
|
||||||
|
.ToHashSetAsync();
|
||||||
|
|
||||||
|
var notifications = await _dbContext.Notification
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(n => !dismissedIds.Contains(n.Id))
|
||||||
|
.Where(n =>
|
||||||
|
string.IsNullOrWhiteSpace(n.Target)
|
||||||
|
|| (n.Target != null && n.Target.StartsWith("pub/"))
|
||||||
|
|| (!string.IsNullOrWhiteSpace(currentUserId) && n.Target == $"user/{currentUserId}")
|
||||||
|
|| (isAdmin && n.Target == "administration"))
|
||||||
|
.OrderByDescending(n => n.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(notifications);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Net.Http.Json;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Yavsc.Abstract.Models.Messaging;
|
||||||
using Yavsc.Abstract.Resources;
|
using Yavsc.Abstract.Resources;
|
||||||
using Yavsc.Api.Test.Fixtures;
|
using Yavsc.Api.Test.Fixtures;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
|
@ -115,6 +116,49 @@ public sealed class ResourceUsageApiControllerTests : IClassFixture<ApiWebServer
|
||||||
Assert.True(record.BandwidthMb > 0m);
|
Assert.True(record.BandwidthMb > 0m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCurrentUserNotifications_returns_public_and_targeted_notifications()
|
||||||
|
{
|
||||||
|
using var scope = _fixture.Services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
||||||
|
|
||||||
|
db.Notification.RemoveRange(db.Notification);
|
||||||
|
await db.Notification.AddRangeAsync(
|
||||||
|
new Notification
|
||||||
|
{
|
||||||
|
title = "Public",
|
||||||
|
body = "Seen by everyone",
|
||||||
|
click_action = "/public",
|
||||||
|
Target = null,
|
||||||
|
},
|
||||||
|
new Notification
|
||||||
|
{
|
||||||
|
title = "User",
|
||||||
|
body = "Private to current user",
|
||||||
|
click_action = "/profile",
|
||||||
|
Target = "user/telemetry-user",
|
||||||
|
},
|
||||||
|
new Notification
|
||||||
|
{
|
||||||
|
title = "Admin",
|
||||||
|
body = "Restricted to administrators",
|
||||||
|
click_action = "/admin",
|
||||||
|
Target = "administration",
|
||||||
|
});
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
using var http = NewClient(_fixture, "telemetry-user");
|
||||||
|
|
||||||
|
var response = await http.GetAsync("/api/v1/notifications/me", TestContext.Current.CancellationToken);
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var notifications = await response.Content.ReadFromJsonAsync<List<Notification>>(TestContext.Current.CancellationToken);
|
||||||
|
Assert.NotNull(notifications);
|
||||||
|
Assert.Contains(notifications!, n => n.title == "Public");
|
||||||
|
Assert.Contains(notifications!, n => n.title == "User");
|
||||||
|
Assert.DoesNotContain(notifications!, n => n.title == "Admin");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetAdminOverview_returns_latest_usage_snapshot_for_each_user()
|
public async Task GetAdminOverview_returns_latest_usage_snapshot_for_each_user()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue