New: User Search and User-Contact UI and Service
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<ActionLink Action="Apply" Text="Ingenieur Antrag hochladen" />
|
<ActionLink Action="Apply" Text="Ingenieur Antrag hochladen" />
|
||||||
|
<ActionLink Action="UserSearch" Text="Mitglieder finden" />
|
||||||
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
<ActionLink Action="AdminReview" Text="Admin Bereich" Security="SecurityAccessLevel.Edit" />
|
<ActionLink Action="AdminReview" Text="Admin Bereich" Security="SecurityAccessLevel.Edit" />
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
@using SZUAbsolventenverein.Module.PremiumArea.Services
|
||||||
|
@using Oqtane.Models
|
||||||
|
@namespace SZUAbsolventenverein.Module.PremiumArea
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject IUserContactService ContactService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, "Premium Member"))
|
||||||
|
{
|
||||||
|
<h3>Mitglieder Suche</h3>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" class="form-control" placeholder="Mitglieder suchen (min 3 Zeichen)..." @bind="_query" @onkeyup="@(e => { if (e.Key == "Enter") Search(); })" />
|
||||||
|
<button class="btn btn-primary" @onclick="Search">Suchen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (_searchResults != null)
|
||||||
|
{
|
||||||
|
@if (_searchResults.Count == 0)
|
||||||
|
{
|
||||||
|
<p class="text-muted">Keine Mitglieder gefunden.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ul class="list-group">
|
||||||
|
@foreach (var user in _searchResults)
|
||||||
|
{
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<span>
|
||||||
|
<strong>@user.DisplayName</strong> <small class="text-muted">(@user.Username)</small>
|
||||||
|
</span>
|
||||||
|
<button class="btn btn-sm btn-outline-info" @onclick="@(() => InitContact(user))">Kontaktieren</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_selectedUser != null)
|
||||||
|
{
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header">Nachricht an: @_selectedUser.DisplayName</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Nachricht</label>
|
||||||
|
<textarea class="form-control" rows="3" @bind="_messageBody"></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" @onclick="Send">Nachricht senden</button>
|
||||||
|
<button class="btn btn-secondary" @onclick="@(() => _selectedUser = null)">Abbrechen</button>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(_statusMsg))
|
||||||
|
{
|
||||||
|
<div class="alert alert-info mt-2">@_statusMsg</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Sie müssen Premium Kunde sein um diese Funktion zu nutzen.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
|
private string _query;
|
||||||
|
private List<User> _searchResults;
|
||||||
|
private User _selectedUser;
|
||||||
|
private string _messageBody;
|
||||||
|
private string _statusMsg;
|
||||||
|
|
||||||
|
private async Task Search()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_query) || _query.Length < 3) return;
|
||||||
|
_searchResults = await ContactService.SearchUsersAsync(_query, ModuleState.ModuleId);
|
||||||
|
_selectedUser = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitContact(User user)
|
||||||
|
{
|
||||||
|
_selectedUser = user;
|
||||||
|
_messageBody = "";
|
||||||
|
_statusMsg = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Send()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_messageBody)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ContactService.SendMessageAsync(_selectedUser.UserId, _messageBody, ModuleState.ModuleId);
|
||||||
|
_statusMsg = "Message Sent Successully!";
|
||||||
|
// Reset after delay or allow closing
|
||||||
|
await Task.Delay(2000);
|
||||||
|
_selectedUser = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_statusMsg = "Error sending message: " + ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Client/Services/UserContactService.cs
Normal file
32
Client/Services/UserContactService.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||||
|
{
|
||||||
|
public interface IUserContactService
|
||||||
|
{
|
||||||
|
Task<List<User>> SearchUsersAsync(string query, int moduleId);
|
||||||
|
Task SendMessageAsync(int recipientUserId, string message, int moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserContactService : ServiceBase, IUserContactService
|
||||||
|
{
|
||||||
|
public UserContactService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||||
|
|
||||||
|
private string Apiurl => CreateApiUrl("UserContact");
|
||||||
|
|
||||||
|
public async Task<List<User>> SearchUsersAsync(string query, int moduleId)
|
||||||
|
{
|
||||||
|
return await GetJsonAsync<List<User>>(CreateAuthorizationPolicyUrl($"{Apiurl}/search/{query}?moduleid={moduleId}", EntityNames.Module, moduleId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendMessageAsync(int recipientUserId, string message, int moduleId)
|
||||||
|
{
|
||||||
|
await PostAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/send?recipientId={recipientUserId}&moduleid={moduleId}&message={System.Net.WebUtility.UrlEncode(message)}", EntityNames.Module, moduleId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,10 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Startup
|
|||||||
{
|
{
|
||||||
services.AddScoped<IEngineerApplicationService, EngineerApplicationService>();
|
services.AddScoped<IEngineerApplicationService, EngineerApplicationService>();
|
||||||
}
|
}
|
||||||
|
if (!services.Any(s => s.ServiceType == typeof(IUserContactService)))
|
||||||
|
{
|
||||||
|
services.AddScoped<IUserContactService, UserContactService>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
Server/Services/ServerUserContactService.cs
Normal file
114
Server/Services/ServerUserContactService.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Security;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using SZUAbsolventenverein.Module.PremiumArea.Models;
|
||||||
|
|
||||||
|
namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||||
|
{
|
||||||
|
public class ServerUserContactService : IUserContactService
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly INotificationRepository _notificationRepository;
|
||||||
|
private readonly IUserPermissions _userPermissions;
|
||||||
|
private readonly ILogManager _logger;
|
||||||
|
private readonly IHttpContextAccessor _accessor;
|
||||||
|
private readonly ITenantManager _tenantManager;
|
||||||
|
private readonly Alias _alias;
|
||||||
|
|
||||||
|
public ServerUserContactService(IUserRepository userRepository, INotificationRepository notificationRepository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
||||||
|
{
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_notificationRepository = notificationRepository;
|
||||||
|
_userPermissions = userPermissions;
|
||||||
|
_logger = logger;
|
||||||
|
_accessor = accessor;
|
||||||
|
_tenantManager = tenantManager;
|
||||||
|
_alias = tenantManager.GetAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<User>> SearchUsersAsync(string query, int moduleId)
|
||||||
|
{
|
||||||
|
// Note: moduleId param added to match Interface if it requires it, or just ignore if interface doesn't have it.
|
||||||
|
// Client interface: Task<List<User>> SearchUsersAsync(string query, int moduleId);
|
||||||
|
// My previous server impl: SearchUsersAsync(string query) -> Mismatch!
|
||||||
|
// I must match the signature of the Interface defined in Client.
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<User>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_accessor.HttpContext.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new List<User>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try GetUsers() without params first
|
||||||
|
var users = _userRepository.GetUsers();
|
||||||
|
var results = users.Where(u =>
|
||||||
|
(u.DisplayName != null && u.DisplayName.Contains(query, StringComparison.OrdinalIgnoreCase)) ||
|
||||||
|
(u.Username != null && u.Username.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||||
|
).Take(20).ToList();
|
||||||
|
|
||||||
|
var sanitized = results.Select(u => new User
|
||||||
|
{
|
||||||
|
UserId = u.UserId,
|
||||||
|
Username = u.Username,
|
||||||
|
DisplayName = u.DisplayName,
|
||||||
|
PhotoFileId = u.PhotoFileId
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Task.FromResult(sanitized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendMessageAsync(int recipientUserId, string message, int moduleId)
|
||||||
|
{
|
||||||
|
var sender = _accessor.HttpContext.User;
|
||||||
|
if (!sender.Identity.IsAuthenticated) return Task.CompletedTask;
|
||||||
|
|
||||||
|
int senderId = _accessor.HttpContext.GetUserId();
|
||||||
|
var recipient = _userRepository.GetUser(recipientUserId);
|
||||||
|
if (recipient == null) return Task.CompletedTask;
|
||||||
|
|
||||||
|
var notification = new Notification
|
||||||
|
{
|
||||||
|
SiteId = _alias.SiteId,
|
||||||
|
FromUserId = senderId,
|
||||||
|
ToUserId = recipientUserId,
|
||||||
|
ToEmail = "",
|
||||||
|
Subject = "New Message from " + sender.Identity.Name,
|
||||||
|
Body = message,
|
||||||
|
ParentId = null,
|
||||||
|
CreatedOn = DateTime.UtcNow,
|
||||||
|
IsDelivered = false,
|
||||||
|
DeliveredOn = null
|
||||||
|
};
|
||||||
|
_notificationRepository.AddNotification(notification);
|
||||||
|
|
||||||
|
var emailNotification = new Notification
|
||||||
|
{
|
||||||
|
SiteId = _alias.SiteId,
|
||||||
|
FromUserId = senderId,
|
||||||
|
ToUserId = recipientUserId,
|
||||||
|
ToEmail = recipient.Email,
|
||||||
|
Subject = $"New Connection Request from {sender.Identity.Name}",
|
||||||
|
Body = $"Hello {recipient.DisplayName},<br><br>{sender.Identity.Name} sent you a message:<br><blockquote>{message}</blockquote><br><br>Login to reply.",
|
||||||
|
ParentId = null,
|
||||||
|
CreatedOn = DateTime.UtcNow,
|
||||||
|
IsDelivered = false
|
||||||
|
};
|
||||||
|
_notificationRepository.AddNotification(emailNotification);
|
||||||
|
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Message sent from {SenderId} to {RecipientId}", senderId, recipientUserId);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Startup
|
|||||||
{
|
{
|
||||||
services.AddTransient<IPremiumAreaService, ServerPremiumAreaService>();
|
services.AddTransient<IPremiumAreaService, ServerPremiumAreaService>();
|
||||||
services.AddTransient<IEngineerApplicationService, ServerEngineerApplicationService>();
|
services.AddTransient<IEngineerApplicationService, ServerEngineerApplicationService>();
|
||||||
|
services.AddTransient<IUserContactService, ServerUserContactService>();
|
||||||
services.AddDbContextFactory<PremiumAreaContext>(opt => { }, ServiceLifetime.Transient);
|
services.AddDbContextFactory<PremiumAreaContext>(opt => { }, ServiceLifetime.Transient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user