diff --git a/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/Index.razor b/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/Index.razor
index 69bc802..55f4a83 100644
--- a/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/Index.razor
+++ b/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/Index.razor
@@ -11,6 +11,7 @@
+
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
diff --git a/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/UserSearch.razor b/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/UserSearch.razor
new file mode 100644
index 0000000..32cbfc3
--- /dev/null
+++ b/Client/Modules/SZUAbsolventenverein.Module.PremiumArea/UserSearch.razor
@@ -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"))
+{
+
Mitglieder Suche
+
+
+ { if (e.Key == "Enter") Search(); })" />
+
+
+
+ @if (_searchResults != null)
+ {
+ @if (_searchResults.Count == 0)
+ {
+
Keine Mitglieder gefunden.
+ }
+ else
+ {
+
+ @foreach (var user in _searchResults)
+ {
+ -
+
+ @user.DisplayName (@user.Username)
+
+
+
+ }
+
+ }
+ }
+
+ @if (_selectedUser != null)
+ {
+
+
+
+
+
+
+
+
+
+
+ @if (!string.IsNullOrEmpty(_statusMsg))
+ {
+
@_statusMsg
+ }
+
+
+ }
+}
+else
+{
+
+ Sie müssen Premium Kunde sein um diese Funktion zu nutzen.
+
+}
+
+@code {
+ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
+
+ private string _query;
+ private List
_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;
+ }
+ }
+}
diff --git a/Client/Services/UserContactService.cs b/Client/Services/UserContactService.cs
new file mode 100644
index 0000000..d6c1873
--- /dev/null
+++ b/Client/Services/UserContactService.cs
@@ -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> 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> SearchUsersAsync(string query, int moduleId)
+ {
+ return await GetJsonAsync>(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));
+ }
+ }
+}
diff --git a/Client/Startup/ClientStartup.cs b/Client/Startup/ClientStartup.cs
index e74faf9..bbf8b0d 100644
--- a/Client/Startup/ClientStartup.cs
+++ b/Client/Startup/ClientStartup.cs
@@ -17,6 +17,10 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Startup
{
services.AddScoped();
}
+ if (!services.Any(s => s.ServiceType == typeof(IUserContactService)))
+ {
+ services.AddScoped();
+ }
}
}
}
diff --git a/Server/Services/ServerUserContactService.cs b/Server/Services/ServerUserContactService.cs
new file mode 100644
index 0000000..5f16c9d
--- /dev/null
+++ b/Server/Services/ServerUserContactService.cs
@@ -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> 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> 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());
+ }
+
+ if (!_accessor.HttpContext.User.Identity.IsAuthenticated)
+ {
+ return Task.FromResult(new List());
+ }
+
+ // 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},
{sender.Identity.Name} sent you a message:
{message}
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;
+ }
+ }
+}
diff --git a/Server/Startup/ServerStartup.cs b/Server/Startup/ServerStartup.cs
index 9068c53..355725b 100644
--- a/Server/Startup/ServerStartup.cs
+++ b/Server/Startup/ServerStartup.cs
@@ -23,7 +23,7 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Startup
{
services.AddTransient();
services.AddTransient();
-
+ services.AddTransient();
services.AddDbContextFactory(opt => { }, ServiceLifetime.Transient);
}
}