From ccf43b04cb39b4bf5a684673e394d911e02a5f38 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sun, 12 Apr 2026 12:43:05 +0000 Subject: [PATCH 1/2] fix: address security and quality scan findings Security fixes: - FriendController: Replace client-supplied userId route params with authenticated user ID from ApiUserContextService; add try/catch for InvalidOperationException in SendFriendRequest; update API routes to remove userId segments - NotificationController: Inject ApiUserContextService and derive userId from auth context instead of route parameters; update routes - ActivityController: Inject ApiUserContextService and use authenticated user ID; ignore userId in ActivityLogRequest body - WishlistController: Use auth context for AddComment, RemoveComment, ReserveItem, CancelReservation; add ownership checks to UpdateWishlist, DeleteWishlist, ShareWishlist, CreateSharingLink, GetWishlistPermissions, RemovePermission; fix CreatedAtAction to use publicId - EventController: Add caller authorization check to AddUserToEvent; update IEventService and EventService to accept and enforce callerId Quality fixes: - WishlistService: Add ILogger dependency with structured logging at creation and access-denial points - OpenWishEmailSender: Fix string interpolation in LogInformation calls; throw InvalidOperationException on send failure instead of silently swallowing it; use LogError for failures - ProductService: Fix string interpolation in LogError calls to use structured logging message templates - NotificationService: Replace direct ApplicationDbContext injection with IDbContextFactory for safe per-operation contexts - OpenAIService: Deserialize ChatResponse and return choices[0].message.content instead of raw response JSON; add null checks with descriptive InvalidOperationException Client updates: - Update FriendHttpClientService, NotificationHttpClientService, ActivityHttpClientService, WishlistHttpClientService, and EventHttpClientService to use the new server-side routes and drop client-supplied user ID fields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Services/EventService.cs | 8 +- .../Services/NotificationService.cs | 35 +++--- .../Services/OpenWishEmailSender.cs | 12 +- .../Services/ProductService.cs | 4 +- .../Services/WishlistService.cs | 7 +- src/OpenWish.Shared/Services/IEventService.cs | 2 +- .../Services/ActivityHttpClientService.cs | 5 +- .../Services/EventHttpClientService.cs | 2 +- .../Services/FriendHttpClientService.cs | 32 +++--- .../Services/NotificationHttpClientService.cs | 9 +- .../Services/WishlistHttpClientService.cs | 15 ++- .../Controllers/ActivityController.cs | 22 ++-- .../Controllers/EventController.cs | 17 ++- .../Controllers/FriendController.cs | 107 ++++++++++++------ .../Controllers/NotificationController.cs | 34 ++++-- .../Controllers/WishlistController.cs | 88 ++++++++++++-- src/OpenWish.Web/Services/OpenAIService.cs | 18 ++- 17 files changed, 297 insertions(+), 120 deletions(-) diff --git a/src/OpenWish.Application/Services/EventService.cs b/src/OpenWish.Application/Services/EventService.cs index 57623b2..3468e2d 100644 --- a/src/OpenWish.Application/Services/EventService.cs +++ b/src/OpenWish.Application/Services/EventService.cs @@ -799,14 +799,20 @@ public async Task DeleteEventByPublicIdAsync(string publicId) await DeleteEventAsync(existingEvent.Id); } - public async Task AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role = "Participant") + public async Task AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role, string callerId) { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var eventEntity = await context.Events + .Include(e => e.CreatedBy) .FirstOrDefaultAsync(e => e.PublicId == eventPublicId && !e.Deleted) ?? throw new KeyNotFoundException($"Event with publicId {eventPublicId} not found"); + if (!IsEventOwner(eventEntity, callerId)) + { + throw new UnauthorizedAccessException("Only the event creator can add users to an event."); + } + return await AddUserToEventAsync(eventEntity.Id, userId, role); } diff --git a/src/OpenWish.Application/Services/NotificationService.cs b/src/OpenWish.Application/Services/NotificationService.cs index 8c33de1..be1e109 100644 --- a/src/OpenWish.Application/Services/NotificationService.cs +++ b/src/OpenWish.Application/Services/NotificationService.cs @@ -8,14 +8,15 @@ namespace OpenWish.Application.Services; -public class NotificationService(ApplicationDbContext context, IMapper mapper) : INotificationService +public class NotificationService(IDbContextFactory contextFactory, IMapper mapper) : INotificationService { - private readonly ApplicationDbContext _context = context; + private readonly IDbContextFactory _contextFactory = contextFactory; private readonly IMapper _mapper = mapper; public async Task> GetUserNotificationsAsync(string userId, bool includeRead = false) { - var query = _context.Notifications + await using var context = await _contextFactory.CreateDbContextAsync(); + var query = context.Notifications .Include(n => n.User) .Where(n => n.UserId == userId && !n.Deleted); @@ -33,12 +34,14 @@ public async Task> GetUserNotificationsAsync(stri public async Task GetUnreadNotificationCountAsync(string userId) { - return await _context.Notifications + await using var context = await _contextFactory.CreateDbContextAsync(); + return await context.Notifications .CountAsync(n => n.UserId == userId && !n.IsRead && !n.Deleted); } public async Task CreateNotificationAsync(string userId, string message) { + await using var context = await _contextFactory.CreateDbContextAsync(); var notification = new Notification { UserId = userId, @@ -49,8 +52,8 @@ public async Task CreateNotificationAsync(string userId, stri UpdatedOn = DateTimeOffset.UtcNow }; - _context.Notifications.Add(notification); - await _context.SaveChangesAsync(); + context.Notifications.Add(notification); + await context.SaveChangesAsync(); return _mapper.Map(notification); } @@ -63,6 +66,7 @@ public async Task CreateNotificationAsync( string type, NotificationActionModel? action = null) { + await using var context = await _contextFactory.CreateDbContextAsync(); var notification = new Notification { UserId = targetUserId, @@ -77,15 +81,16 @@ public async Task CreateNotificationAsync( UpdatedOn = DateTimeOffset.UtcNow }; - _context.Notifications.Add(notification); - await _context.SaveChangesAsync(); + context.Notifications.Add(notification); + await context.SaveChangesAsync(); return _mapper.Map(notification); } public async Task MarkNotificationAsReadAsync(int notificationId) { - var notification = await _context.Notifications.FindAsync(notificationId); + await using var context = await _contextFactory.CreateDbContextAsync(); + var notification = await context.Notifications.FindAsync(notificationId); if (notification == null || notification.Deleted) { @@ -95,13 +100,14 @@ public async Task MarkNotificationAsReadAsync(int notificationId) notification.IsRead = true; notification.UpdatedOn = DateTimeOffset.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); return true; } public async Task MarkAllNotificationsAsReadAsync(string userId) { - var notifications = await _context.Notifications + await using var context = await _contextFactory.CreateDbContextAsync(); + var notifications = await context.Notifications .Where(n => n.UserId == userId && !n.IsRead && !n.Deleted) .ToListAsync(); @@ -116,13 +122,14 @@ public async Task MarkAllNotificationsAsReadAsync(string userId) notification.UpdatedOn = DateTimeOffset.UtcNow; } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); return true; } public async Task DeleteNotificationAsync(int notificationId) { - var notification = await _context.Notifications.FindAsync(notificationId); + await using var context = await _contextFactory.CreateDbContextAsync(); + var notification = await context.Notifications.FindAsync(notificationId); if (notification == null) { @@ -132,7 +139,7 @@ public async Task DeleteNotificationAsync(int notificationId) notification.Deleted = true; notification.UpdatedOn = DateTimeOffset.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); return true; } } \ No newline at end of file diff --git a/src/OpenWish.Application/Services/OpenWishEmailSender.cs b/src/OpenWish.Application/Services/OpenWishEmailSender.cs index 0ca5277..fdfcc1b 100644 --- a/src/OpenWish.Application/Services/OpenWishEmailSender.cs +++ b/src/OpenWish.Application/Services/OpenWishEmailSender.cs @@ -114,9 +114,15 @@ public async Task SendEmailAsync(string toEmail, string subject, string message) .Body(message, true) .SendAsync(); - _logger.LogInformation(response.Successful - ? $"Email to {toEmail} queued successfully!" - : $"Failure sending email to {toEmail}"); + if (response.Successful) + { + _logger.LogInformation("Email to {Email} queued successfully!", toEmail); + } + else + { + _logger.LogError("Failure sending email to {Email}: {Errors}", toEmail, string.Join(", ", response.ErrorMessages)); + throw new InvalidOperationException($"Failed to send email to {toEmail}."); + } } private string WrapInHtmlFormattedEmail(string message) diff --git a/src/OpenWish.Application/Services/ProductService.cs b/src/OpenWish.Application/Services/ProductService.cs index f1b659f..82c2e3e 100644 --- a/src/OpenWish.Application/Services/ProductService.cs +++ b/src/OpenWish.Application/Services/ProductService.cs @@ -124,12 +124,12 @@ private static async Task IsSafeUrlAsync(Uri uri) } catch (HttpRequestException ex) { - _logger.LogError(ex, $"Error fetching URL: {ex.Message}"); + _logger.LogError(ex, "Error fetching URL: {Message}", ex.Message); return null; } catch (Exception ex) { - _logger.LogError(ex, $"Error parsing HTML: {ex.Message}"); + _logger.LogError(ex, "Error parsing HTML: {Message}", ex.Message); return null; } } diff --git a/src/OpenWish.Application/Services/WishlistService.cs b/src/OpenWish.Application/Services/WishlistService.cs index a6ca2e0..ab52975 100644 --- a/src/OpenWish.Application/Services/WishlistService.cs +++ b/src/OpenWish.Application/Services/WishlistService.cs @@ -3,6 +3,7 @@ using System.Linq; using AutoMapper; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using OpenWish.Data; using OpenWish.Data.Entities; using OpenWish.Shared.Models; @@ -10,11 +11,12 @@ namespace OpenWish.Application.Services; -public class WishlistService(IDbContextFactory contextFactory, IMapper mapper, IActivityService activityService) : IWishlistService +public class WishlistService(IDbContextFactory contextFactory, IMapper mapper, IActivityService activityService, ILogger logger) : IWishlistService { private readonly IDbContextFactory _contextFactory = contextFactory; private readonly IMapper _mapper = mapper; private readonly IActivityService _activityService = activityService; + private readonly ILogger _logger = logger; public async Task CreateWishlistAsync(WishlistModel wishlistModel, string ownerId) { @@ -41,6 +43,8 @@ public async Task CreateWishlistAsync(WishlistModel wishlistModel var entry = context.Wishlists.Add(wishlistEntity); await context.SaveChangesAsync(); + _logger.LogInformation("Wishlist '{Name}' created by user {UserId}", wishlistEntity.Name, ownerId); + // Log activity await _activityService.LogActivityAsync( ownerId, @@ -101,6 +105,7 @@ public async Task GetWishlistByPublicIdAsync(string publicId, str var canAccess = await CanUserAccessWishlistInternalAsync(context, wishlistEntity.Id, userId); if (!canAccess) { + _logger.LogWarning("Access denied: user {UserId} attempted to access wishlist {PublicId}", userId, publicId); throw new UnauthorizedAccessException($"Access denied to wishlist {publicId}"); } } diff --git a/src/OpenWish.Shared/Services/IEventService.cs b/src/OpenWish.Shared/Services/IEventService.cs index 6e5cd79..09e1cd7 100644 --- a/src/OpenWish.Shared/Services/IEventService.cs +++ b/src/OpenWish.Shared/Services/IEventService.cs @@ -13,7 +13,7 @@ public interface IEventService Task DeleteEventAsync(int id); Task DeleteEventByPublicIdAsync(string publicId); Task AddUserToEventAsync(int eventId, string userId, string role = "Participant"); - Task AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role = "Participant"); + Task AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role, string callerId); Task RemoveUserFromEventAsync(int eventId, string userId, string requestorId); Task RemoveUserFromEventByPublicIdAsync(string eventPublicId, string userId, string requestorId); Task> GetEventWishlistsAsync(int eventId, string? requestingUserId = null); diff --git a/src/OpenWish.Web.Client/Services/ActivityHttpClientService.cs b/src/OpenWish.Web.Client/Services/ActivityHttpClientService.cs index 229efc6..6faae4b 100644 --- a/src/OpenWish.Web.Client/Services/ActivityHttpClientService.cs +++ b/src/OpenWish.Web.Client/Services/ActivityHttpClientService.cs @@ -18,7 +18,6 @@ public async Task LogActivityAsync( { var activity = new { - UserId = userId, ActivityType = activityType, Description = description, WishlistId = wishlistId, @@ -33,13 +32,13 @@ public async Task LogActivityAsync( public async Task> GetUserActivityFeedAsync(string userId, int count = 20, int skip = 0) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/user/{userId}?count={count}&skip={skip}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/user?count={count}&skip={skip}") ?? Array.Empty(); } public async Task> GetFriendsActivityFeedAsync(string userId, int count = 20, int skip = 0) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/friends/{userId}?count={count}&skip={skip}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/friends?count={count}&skip={skip}") ?? Array.Empty(); } diff --git a/src/OpenWish.Web.Client/Services/EventHttpClientService.cs b/src/OpenWish.Web.Client/Services/EventHttpClientService.cs index 5329461..55ddece 100644 --- a/src/OpenWish.Web.Client/Services/EventHttpClientService.cs +++ b/src/OpenWish.Web.Client/Services/EventHttpClientService.cs @@ -183,7 +183,7 @@ public async Task DeleteEventByPublicIdAsync(string publicId) response.EnsureSuccessStatusCode(); } - public async Task AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role = "Participant") + public async Task AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role, string callerId) { var request = new { UserId = userId, Role = role }; var response = await httpClient.PostAsJsonAsync($"api/events/{eventPublicId}/users", request); diff --git a/src/OpenWish.Web.Client/Services/FriendHttpClientService.cs b/src/OpenWish.Web.Client/Services/FriendHttpClientService.cs index 680a202..9f74dc0 100644 --- a/src/OpenWish.Web.Client/Services/FriendHttpClientService.cs +++ b/src/OpenWish.Web.Client/Services/FriendHttpClientService.cs @@ -11,25 +11,25 @@ public class FriendHttpClientService(HttpClient httpClient) : IFriendService public async Task> GetFriendsAsync(string userId) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/user/{userId}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/user") ?? Array.Empty(); } public async Task AreFriendsAsync(string userId, string otherUserId) { - return await _httpClient.GetFromJsonAsync($"{BaseUrl}/check/{userId}/{otherUserId}"); + return await _httpClient.GetFromJsonAsync($"{BaseUrl}/check/{otherUserId}"); } public async Task RemoveFriendAsync(string userId, string friendId) { - var response = await _httpClient.DeleteAsync($"{BaseUrl}/{userId}/{friendId}"); + var response = await _httpClient.DeleteAsync($"{BaseUrl}/{friendId}"); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } public async Task SendFriendRequestAsync(string requesterId, string receiverId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requesterId}/{receiverId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/request/{receiverId}", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new HttpRequestException("Failed to send friend request"); @@ -37,33 +37,33 @@ public async Task SendFriendRequestAsync(string requesterId, public async Task> GetReceivedFriendRequestsAsync(string userId) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/requests/received/{userId}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/requests/received") ?? Array.Empty(); } public async Task> GetSentFriendRequestsAsync(string userId) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/requests/sent/{userId}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/requests/sent") ?? Array.Empty(); } public async Task AcceptFriendRequestAsync(int requestId, string userId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/accept/{userId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/accept", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } public async Task RejectFriendRequestAsync(int requestId, string userId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/reject/{userId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/reject", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } public async Task CancelFriendRequestAsync(int requestId, string requesterId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/cancel/{requesterId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/cancel", null); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { return false; @@ -75,7 +75,7 @@ public async Task CancelFriendRequestAsync(int requestId, string requester public async Task ResendFriendRequestAsync(int requestId, string requesterId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/resend/{requesterId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/request/{requestId}/resend", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new HttpRequestException("Failed to resend friend request"); @@ -83,34 +83,34 @@ public async Task ResendFriendRequestAsync(int requestId, st public async Task SendFriendInviteByEmailAsync(string senderUserId, string emailAddress) { - var response = await _httpClient.PostAsync($"{BaseUrl}/invite/{senderUserId}?email={Uri.EscapeDataString(emailAddress)}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/invite?email={Uri.EscapeDataString(emailAddress)}", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } public async Task SendFriendInvitesByEmailAsync(string senderUserId, IEnumerable emailAddresses) { - var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/invite/{senderUserId}/batch", emailAddresses); + var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/invite/batch", emailAddresses); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } public async Task CreateFriendshipFromInviteAsync(string newUserId, string inviterUserId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/invite/complete/{newUserId}/{inviterUserId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/invite/complete/{inviterUserId}", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } public async Task> GetPendingFriendInvitesAsync(string userId) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/pending-invites/{userId}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/pending-invites") ?? Array.Empty(); } public async Task CancelPendingFriendInviteAsync(int inviteId, string userId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/pending-invite/{inviteId}/cancel/{userId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/pending-invite/{inviteId}/cancel", null); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { return false; @@ -122,7 +122,7 @@ public async Task CancelPendingFriendInviteAsync(int inviteId, string user public async Task ResendPendingFriendInviteAsync(int inviteId, string userId) { - var response = await _httpClient.PostAsync($"{BaseUrl}/pending-invite/{inviteId}/resend/{userId}", null); + var response = await _httpClient.PostAsync($"{BaseUrl}/pending-invite/{inviteId}/resend", null); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { return false; diff --git a/src/OpenWish.Web.Client/Services/NotificationHttpClientService.cs b/src/OpenWish.Web.Client/Services/NotificationHttpClientService.cs index 0bd1020..60cda4b 100644 --- a/src/OpenWish.Web.Client/Services/NotificationHttpClientService.cs +++ b/src/OpenWish.Web.Client/Services/NotificationHttpClientService.cs @@ -11,18 +11,18 @@ public class NotificationHttpClientService(HttpClient httpClient) : INotificatio public async Task> GetUserNotificationsAsync(string userId, bool includeRead = false) { - return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/user/{userId}?includeRead={includeRead}") + return await _httpClient.GetFromJsonAsync>($"{BaseUrl}/user?includeRead={includeRead}") ?? Array.Empty(); } public async Task GetUnreadNotificationCountAsync(string userId) { - return await _httpClient.GetFromJsonAsync($"{BaseUrl}/user/{userId}/count"); + return await _httpClient.GetFromJsonAsync($"{BaseUrl}/count"); } public async Task CreateNotificationAsync(string userId, string message) { - var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/user/{userId}", message); + var response = await _httpClient.PostAsJsonAsync(BaseUrl, message); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync() ?? throw new HttpRequestException("Failed to create notification"); @@ -38,7 +38,6 @@ public async Task CreateNotificationAsync( { var notificationData = new { - SenderUserId = senderUserId, Title = title, Message = message, Type = type, @@ -60,7 +59,7 @@ public async Task MarkNotificationAsReadAsync(int notificationId) public async Task MarkAllNotificationsAsReadAsync(string userId) { - var response = await _httpClient.PutAsync($"{BaseUrl}/user/{userId}/read-all", null); + var response = await _httpClient.PutAsync($"{BaseUrl}/read-all", null); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } diff --git a/src/OpenWish.Web.Client/Services/WishlistHttpClientService.cs b/src/OpenWish.Web.Client/Services/WishlistHttpClientService.cs index f8217db..ede6c05 100644 --- a/src/OpenWish.Web.Client/Services/WishlistHttpClientService.cs +++ b/src/OpenWish.Web.Client/Services/WishlistHttpClientService.cs @@ -132,10 +132,9 @@ public async Task CanUserEditWishlistAsync(int wishlistId, string userId) return await _httpClient.GetFromJsonAsync($"{BaseUrl}/{wishlistId}/can-edit/{userId}"); } - // Item comments public async Task AddCommentToItemAsync(int wishlistId, int itemId, string userId, string text) { - var commentRequest = new { userId, text }; + var commentRequest = new { text }; var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/{wishlistId}/items/{itemId}/comments", commentRequest); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); @@ -148,7 +147,7 @@ public async Task> GetItemCommentsAsync(int wishli public async Task RemoveItemCommentAsync(int commentId, string userId) { - var response = await _httpClient.DeleteAsync($"{BaseUrl}/comments/{commentId}?userId={userId}"); + var response = await _httpClient.DeleteAsync($"{BaseUrl}/comments/{commentId}"); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } @@ -156,7 +155,7 @@ public async Task RemoveItemCommentAsync(int commentId, string userId) // Item reservations public async Task ReserveItemAsync(int wishlistId, int itemId, string userId, bool isAnonymous = false) { - var reservationRequest = new { userId, isAnonymous }; + var reservationRequest = new { isAnonymous }; var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/{wishlistId}/items/{itemId}/reserve", reservationRequest); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); @@ -164,7 +163,7 @@ public async Task ReserveItemAsync(int wishlistId, int itemId, string user public async Task CancelReservationAsync(int wishlistId, int itemId, string userId) { - var response = await _httpClient.DeleteAsync($"{BaseUrl}/{wishlistId}/items/{itemId}/reservation?userId={userId}"); + var response = await _httpClient.DeleteAsync($"{BaseUrl}/{wishlistId}/items/{itemId}/reservation"); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } @@ -276,7 +275,7 @@ public async Task UpdateWishlistItemByPublicIdAsync(string wi public async Task AddCommentToItemByPublicIdAsync(string wishlistPublicId, int itemId, string userId, string text) { - var request = new { UserId = userId, Text = text }; + var request = new { Text = text }; var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/{wishlistPublicId}/items/{itemId}/comments", request); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); @@ -289,14 +288,14 @@ public async Task> GetItemCommentsByPublicIdAsync( public async Task ReserveItemByPublicIdAsync(string wishlistPublicId, int itemId, string userId, bool isAnonymous = false) { - var request = new { UserId = userId, IsAnonymous = isAnonymous }; + var request = new { IsAnonymous = isAnonymous }; var response = await _httpClient.PostAsJsonAsync($"{BaseUrl}/{wishlistPublicId}/items/{itemId}/reserve", request); return response.IsSuccessStatusCode; } public async Task CancelReservationByPublicIdAsync(string wishlistPublicId, int itemId, string userId) { - var response = await _httpClient.DeleteAsync($"{BaseUrl}/{wishlistPublicId}/items/{itemId}/reservation?userId={userId}"); + var response = await _httpClient.DeleteAsync($"{BaseUrl}/{wishlistPublicId}/items/{itemId}/reservation"); return response.IsSuccessStatusCode; } diff --git a/src/OpenWish.Web/Controllers/ActivityController.cs b/src/OpenWish.Web/Controllers/ActivityController.cs index 46fa111..dee62b8 100644 --- a/src/OpenWish.Web/Controllers/ActivityController.cs +++ b/src/OpenWish.Web/Controllers/ActivityController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenWish.Shared.Services; +using OpenWish.Web.Services; namespace OpenWish.Web.Controllers; @@ -10,22 +11,28 @@ namespace OpenWish.Web.Controllers; public class ActivityController : ControllerBase { private readonly IActivityService _activityService; + private readonly ApiUserContextService _userContextService; - public ActivityController(IActivityService activityService) + public ActivityController(IActivityService activityService, ApiUserContextService userContextService) { _activityService = activityService; + _userContextService = userContextService; } - [HttpGet("user/{userId}")] - public async Task GetUserActivities(string userId, [FromQuery] int count = 20, [FromQuery] int skip = 0) + [HttpGet("user")] + public async Task GetUserActivities([FromQuery] int count = 20, [FromQuery] int skip = 0) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var activities = await _activityService.GetUserActivityFeedAsync(userId, count, skip); return Ok(activities); } - [HttpGet("friends/{userId}")] - public async Task GetFriendsActivities(string userId, [FromQuery] int count = 20, [FromQuery] int skip = 0) + [HttpGet("friends")] + public async Task GetFriendsActivities([FromQuery] int count = 20, [FromQuery] int skip = 0) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var activities = await _activityService.GetFriendsActivityFeedAsync(userId, count, skip); return Ok(activities); } @@ -40,8 +47,10 @@ public async Task GetWishlistActivities(int wishlistId, [FromQuer [HttpPost] public async Task LogActivity([FromBody] ActivityLogRequest request) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var activity = await _activityService.LogActivityAsync( - request.UserId, + userId, request.ActivityType, request.Description, request.WishlistId, @@ -52,7 +61,6 @@ public async Task LogActivity([FromBody] ActivityLogRequest reque public class ActivityLogRequest { - public string UserId { get; set; } public string ActivityType { get; set; } public string Description { get; set; } public int? WishlistId { get; set; } diff --git a/src/OpenWish.Web/Controllers/EventController.cs b/src/OpenWish.Web/Controllers/EventController.cs index 2bec5bb..e5ba37f 100644 --- a/src/OpenWish.Web/Controllers/EventController.cs +++ b/src/OpenWish.Web/Controllers/EventController.cs @@ -85,12 +85,21 @@ public async Task DeleteEvent(string publicId) [HttpPost("{eventPublicId}/users")] public async Task AddUserToEvent(string eventPublicId, [FromBody] AddUserToEventRequest request) { - var result = await _eventService.AddUserToEventByPublicIdAsync(eventPublicId, request.UserId, request.Role); - if (!result) + var callerId = await _userContextService.GetUserIdAsync(); + if (callerId is null) return Unauthorized(); + try { - return NotFound(); + var result = await _eventService.AddUserToEventByPublicIdAsync(eventPublicId, request.UserId, request.Role, callerId); + if (!result) + { + return NotFound(); + } + return NoContent(); + } + catch (UnauthorizedAccessException ex) + { + return Forbid(ex.Message); } - return NoContent(); } [HttpDelete("{eventPublicId}/users/{userId}")] diff --git a/src/OpenWish.Web/Controllers/FriendController.cs b/src/OpenWish.Web/Controllers/FriendController.cs index fdd1a59..0411753 100644 --- a/src/OpenWish.Web/Controllers/FriendController.cs +++ b/src/OpenWish.Web/Controllers/FriendController.cs @@ -19,72 +19,99 @@ public FriendController(IFriendService friendService, IUserContextService userCo _userContextService = userContextService; } - [HttpGet("user/{userId}")] - public async Task GetFriends(string userId) + [HttpGet("user")] + public async Task GetFriends() { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var friends = await _friendService.GetFriendsAsync(userId); return Ok(friends); } - [HttpGet("check/{userId}/{otherUserId}")] - public async Task CheckFriendship(string userId, string otherUserId) + [HttpGet("check/{otherUserId}")] + public async Task CheckFriendship(string otherUserId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var areFriends = await _friendService.AreFriendsAsync(userId, otherUserId); return Ok(areFriends); } - [HttpDelete("{userId}/{friendId}")] - public async Task RemoveFriend(string userId, string friendId) + [HttpDelete("{friendId}")] + public async Task RemoveFriend(string friendId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _friendService.RemoveFriendAsync(userId, friendId); return Ok(result); } - [HttpPost("request/{requesterId}/{receiverId}")] - public async Task SendFriendRequest(string requesterId, string receiverId) + [HttpPost("request/{receiverId}")] + public async Task SendFriendRequest(string receiverId) { - var request = await _friendService.SendFriendRequestAsync(requesterId, receiverId); - return Ok(request); + var requesterId = await _userContextService.GetUserIdAsync(); + if (requesterId is null) return Unauthorized(); + try + { + var request = await _friendService.SendFriendRequestAsync(requesterId, receiverId); + return Ok(request); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } } - [HttpGet("requests/received/{userId}")] - public async Task GetReceivedRequests(string userId) + [HttpGet("requests/received")] + public async Task GetReceivedRequests() { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var requests = await _friendService.GetReceivedFriendRequestsAsync(userId); return Ok(requests); } - [HttpGet("requests/sent/{userId}")] - public async Task GetSentRequests(string userId) + [HttpGet("requests/sent")] + public async Task GetSentRequests() { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var requests = await _friendService.GetSentFriendRequestsAsync(userId); return Ok(requests); } - [HttpPost("request/{requestId}/accept/{userId}")] - public async Task AcceptRequest(int requestId, string userId) + [HttpPost("request/{requestId}/accept")] + public async Task AcceptRequest(int requestId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _friendService.AcceptFriendRequestAsync(requestId, userId); return Ok(result); } - [HttpPost("request/{requestId}/reject/{userId}")] - public async Task RejectRequest(int requestId, string userId) + [HttpPost("request/{requestId}/reject")] + public async Task RejectRequest(int requestId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _friendService.RejectFriendRequestAsync(requestId, userId); return Ok(result); } - [HttpPost("request/{requestId}/cancel/{userId}")] - public async Task CancelRequest(int requestId, string userId) + [HttpPost("request/{requestId}/cancel")] + public async Task CancelRequest(int requestId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _friendService.CancelFriendRequestAsync(requestId, userId); return result ? Ok(true) : NotFound(); } - [HttpPost("request/{requestId}/resend/{userId}")] - public async Task ResendRequest(int requestId, string userId) + [HttpPost("request/{requestId}/resend")] + public async Task ResendRequest(int requestId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); try { var updatedRequest = await _friendService.ResendFriendRequestAsync(requestId, userId); @@ -96,9 +123,11 @@ public async Task ResendRequest(int requestId, string userId) } } - [HttpPost("invite/{senderUserId}")] - public async Task SendFriendInviteByEmail(string senderUserId, [FromQuery] string email) + [HttpPost("invite")] + public async Task SendFriendInviteByEmail([FromQuery] string email) { + var senderUserId = await _userContextService.GetUserIdAsync(); + if (senderUserId is null) return Unauthorized(); try { var result = await _friendService.SendFriendInviteByEmailAsync(senderUserId, email); @@ -110,9 +139,11 @@ public async Task SendFriendInviteByEmail(string senderUserId, [F } } - [HttpPost("invite/{senderUserId}/batch")] - public async Task SendFriendInvitesByEmail(string senderUserId, [FromBody] List emails) + [HttpPost("invite/batch")] + public async Task SendFriendInvitesByEmail([FromBody] List emails) { + var senderUserId = await _userContextService.GetUserIdAsync(); + if (senderUserId is null) return Unauthorized(); try { var result = await _friendService.SendFriendInvitesByEmailAsync(senderUserId, emails); @@ -124,9 +155,11 @@ public async Task SendFriendInvitesByEmail(string senderUserId, [ } } - [HttpPost("invite/complete/{newUserId}/{inviterUserId}")] - public async Task CreateFriendshipFromInvite(string newUserId, string inviterUserId) + [HttpPost("invite/complete/{inviterUserId}")] + public async Task CreateFriendshipFromInvite(string inviterUserId) { + var newUserId = await _userContextService.GetUserIdAsync(); + if (newUserId is null) return Unauthorized(); try { var result = await _friendService.CreateFriendshipFromInviteAsync(newUserId, inviterUserId); @@ -138,23 +171,29 @@ public async Task CreateFriendshipFromInvite(string newUserId, st } } - [HttpGet("pending-invites/{userId}")] - public async Task GetPendingFriendInvites(string userId) + [HttpGet("pending-invites")] + public async Task GetPendingFriendInvites() { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var invites = await _friendService.GetPendingFriendInvitesAsync(userId); return Ok(invites); } - [HttpPost("pending-invite/{inviteId}/cancel/{userId}")] - public async Task CancelPendingFriendInvite(int inviteId, string userId) + [HttpPost("pending-invite/{inviteId}/cancel")] + public async Task CancelPendingFriendInvite(int inviteId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _friendService.CancelPendingFriendInviteAsync(inviteId, userId); return result ? Ok(true) : NotFound(); } - [HttpPost("pending-invite/{inviteId}/resend/{userId}")] - public async Task ResendPendingFriendInvite(int inviteId, string userId) + [HttpPost("pending-invite/{inviteId}/resend")] + public async Task ResendPendingFriendInvite(int inviteId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _friendService.ResendPendingFriendInviteAsync(inviteId, userId); return result ? Ok(true) : NotFound(); } diff --git a/src/OpenWish.Web/Controllers/NotificationController.cs b/src/OpenWish.Web/Controllers/NotificationController.cs index 082fbb5..37c6d44 100644 --- a/src/OpenWish.Web/Controllers/NotificationController.cs +++ b/src/OpenWish.Web/Controllers/NotificationController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using OpenWish.Shared.Models; using OpenWish.Shared.Services; +using OpenWish.Web.Services; namespace OpenWish.Web.Controllers; @@ -11,29 +12,37 @@ namespace OpenWish.Web.Controllers; public class NotificationController : ControllerBase { private readonly INotificationService _notificationService; + private readonly ApiUserContextService _userContextService; - public NotificationController(INotificationService notificationService) + public NotificationController(INotificationService notificationService, ApiUserContextService userContextService) { _notificationService = notificationService; + _userContextService = userContextService; } - [HttpGet("user/{userId}")] - public async Task GetUserNotifications(string userId, [FromQuery] bool includeRead = false) + [HttpGet("user")] + public async Task GetUserNotifications([FromQuery] bool includeRead = false) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var notifications = await _notificationService.GetUserNotificationsAsync(userId, includeRead); return Ok(notifications); } - [HttpGet("user/{userId}/count")] - public async Task GetUnreadNotificationCount(string userId) + [HttpGet("count")] + public async Task GetUnreadNotificationCount() { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var count = await _notificationService.GetUnreadNotificationCountAsync(userId); return Ok(count); } - [HttpPost("user/{userId}")] - public async Task CreateNotification(string userId, [FromBody] string message) + [HttpPost] + public async Task CreateNotification([FromBody] string message) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var notification = await _notificationService.CreateNotificationAsync(userId, message); return Ok(notification); } @@ -41,8 +50,10 @@ public async Task CreateNotification(string userId, [FromBody] st [HttpPost("user/{targetUserId}/detailed")] public async Task CreateDetailedNotification(string targetUserId, [FromBody] DetailedNotificationRequest request) { + var senderUserId = await _userContextService.GetUserIdAsync(); + if (senderUserId is null) return Unauthorized(); var notification = await _notificationService.CreateNotificationAsync( - request.SenderUserId, + senderUserId, targetUserId, request.Title, request.Message, @@ -59,9 +70,11 @@ public async Task MarkAsRead(int notificationId) return Ok(result); } - [HttpPut("user/{userId}/read-all")] - public async Task MarkAllAsRead(string userId) + [HttpPut("read-all")] + public async Task MarkAllAsRead() { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _notificationService.MarkAllNotificationsAsReadAsync(userId); return Ok(result); } @@ -76,7 +89,6 @@ public async Task DeleteNotification(int notificationId) public class DetailedNotificationRequest { - public string SenderUserId { get; set; } = string.Empty; public string Title { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; diff --git a/src/OpenWish.Web/Controllers/WishlistController.cs b/src/OpenWish.Web/Controllers/WishlistController.cs index d874cb1..74fd1fa 100644 --- a/src/OpenWish.Web/Controllers/WishlistController.cs +++ b/src/OpenWish.Web/Controllers/WishlistController.cs @@ -59,12 +59,16 @@ public async Task> CreateWishlist(WishlistModel wish return Unauthorized(); } var createdWishlist = await _wishlistService.CreateWishlistAsync(wishlist, userId); - return CreatedAtAction(nameof(GetWishlist), new { id = createdWishlist.Id }, createdWishlist); + return CreatedAtAction(nameof(GetWishlist), new { publicId = createdWishlist.PublicId }, createdWishlist); } [HttpPut("{publicId}")] public async Task UpdateWishlist(string publicId, WishlistModel wishlist) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + var canEdit = await _wishlistService.CanUserEditWishlistByPublicIdAsync(publicId, userId); + if (!canEdit) return Forbid(); var updatedWishlist = await _wishlistService.UpdateWishlistByPublicIdAsync(publicId, wishlist); if (updatedWishlist == null) { @@ -76,6 +80,10 @@ public async Task UpdateWishlist(string publicId, WishlistModel w [HttpDelete("{publicId}")] public async Task DeleteWishlist(string publicId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + var canEdit = await _wishlistService.CanUserEditWishlistByPublicIdAsync(publicId, userId); + if (!canEdit) return Forbid(); await _wishlistService.DeleteWishlistByPublicIdAsync(publicId); return NoContent(); } @@ -228,6 +236,21 @@ public async Task RemoveItemFromWishlist(string wishlistPublicId, [HttpPost("{wishlistPublicId}/permissions")] public async Task> ShareWishlist(string wishlistPublicId, [FromBody] ShareRequest request) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + try + { + var wishlist = await _wishlistService.GetWishlistByPublicIdAsync(wishlistPublicId, userId); + if (wishlist.OwnerId != userId) return Forbid(); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } var permission = await _wishlistService.ShareWishlistByPublicIdAsync(wishlistPublicId, request.UserId, request.PermissionType); return Ok(permission); } @@ -235,6 +258,21 @@ public async Task> ShareWishlist(string wi [HttpPost("{wishlistPublicId}/share-link")] public async Task> CreateSharingLink(string wishlistPublicId, [FromBody] SharingLinkRequest request) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + try + { + var wishlist = await _wishlistService.GetWishlistByPublicIdAsync(wishlistPublicId, userId); + if (wishlist.OwnerId != userId) return Forbid(); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } var token = await _wishlistService.CreateSharingLinkByPublicIdAsync(wishlistPublicId, request.PermissionType, request.Expiration); return Ok(token); } @@ -249,6 +287,21 @@ public async Task> AcceptSharingLink(string token, [FromBody] [HttpGet("{wishlistPublicId}/permissions")] public async Task>> GetWishlistPermissions(string wishlistPublicId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + try + { + var wishlist = await _wishlistService.GetWishlistByPublicIdAsync(wishlistPublicId, userId); + if (wishlist.OwnerId != userId) return Forbid(); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } var permissions = await _wishlistService.GetWishlistPermissionsByPublicIdAsync(wishlistPublicId); return Ok(permissions); } @@ -256,6 +309,21 @@ public async Task>> GetWishlis [HttpDelete("{wishlistPublicId}/permissions/{userId}")] public async Task> RemovePermission(string wishlistPublicId, string userId) { + var callerId = await _userContextService.GetUserIdAsync(); + if (callerId is null) return Unauthorized(); + try + { + var wishlist = await _wishlistService.GetWishlistByPublicIdAsync(wishlistPublicId, callerId); + if (wishlist.OwnerId != callerId) return Forbid(); + } + catch (KeyNotFoundException) + { + return NotFound(); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } var result = await _wishlistService.RemoveWishlistPermissionByPublicIdAsync(wishlistPublicId, userId); return Ok(result); } @@ -304,7 +372,9 @@ public async Task> CanUserEdit(string wishlistPublicId, strin [HttpPost("{wishlistPublicId}/items/{itemId}/comments")] public async Task> AddComment(string wishlistPublicId, int itemId, [FromBody] CommentRequest request) { - var comment = await _wishlistService.AddCommentToItemByPublicIdAsync(wishlistPublicId, itemId, request.UserId, request.Text); + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + var comment = await _wishlistService.AddCommentToItemByPublicIdAsync(wishlistPublicId, itemId, userId, request.Text); return Ok(comment); } @@ -316,8 +386,10 @@ public async Task>> GetComments(strin } [HttpDelete("comments/{commentId}")] - public async Task> RemoveComment(int commentId, [FromQuery] string userId) + public async Task> RemoveComment(int commentId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _wishlistService.RemoveItemCommentAsync(commentId, userId); return Ok(result); } @@ -326,13 +398,17 @@ public async Task> RemoveComment(int commentId, [FromQuery] s [HttpPost("{wishlistPublicId}/items/{itemId}/reserve")] public async Task> ReserveItem(string wishlistPublicId, int itemId, [FromBody] ReservationRequest request) { - var result = await _wishlistService.ReserveItemByPublicIdAsync(wishlistPublicId, itemId, request.UserId, request.IsAnonymous); + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); + var result = await _wishlistService.ReserveItemByPublicIdAsync(wishlistPublicId, itemId, userId, request.IsAnonymous); return Ok(result); } [HttpDelete("{wishlistPublicId}/items/{itemId}/reservation")] - public async Task> CancelReservation(string wishlistPublicId, int itemId, [FromQuery] string userId) + public async Task> CancelReservation(string wishlistPublicId, int itemId) { + var userId = await _userContextService.GetUserIdAsync(); + if (userId is null) return Unauthorized(); var result = await _wishlistService.CancelReservationByPublicIdAsync(wishlistPublicId, itemId, userId); return Ok(result); } @@ -401,13 +477,11 @@ public class AcceptLinkRequest public class CommentRequest { - public string UserId { get; set; } public string Text { get; set; } } public class ReservationRequest { - public string UserId { get; set; } public bool IsAnonymous { get; set; } } } \ No newline at end of file diff --git a/src/OpenWish.Web/Services/OpenAIService.cs b/src/OpenWish.Web/Services/OpenAIService.cs index 62bcfe8..e17e747 100644 --- a/src/OpenWish.Web/Services/OpenAIService.cs +++ b/src/OpenWish.Web/Services/OpenAIService.cs @@ -1,3 +1,5 @@ +using System.Net.Http.Json; + namespace OpenWish.Web.Services; public interface IOpenAIService @@ -52,9 +54,21 @@ public async Task GenerateCompletionToJsonAsync(string model, List() + ?? throw new InvalidOperationException("Received a null response from the OpenAI API."); + + if (chatResponse.Choices is null || chatResponse.Choices.Count == 0) + { + throw new InvalidOperationException("OpenAI API response contained no choices."); + } + + var content = chatResponse.Choices[0].Message?.Content; + if (string.IsNullOrEmpty(content)) + { + throw new InvalidOperationException("OpenAI API response message content was empty or null."); + } - return responseContent; + return content; } } From 9e5ff5e5d2b7109f607d084e9ce87cb17dd37829 Mon Sep 17 00:00:00 2001 From: Mitchell Barry Date: Sun, 12 Apr 2026 08:14:15 -0500 Subject: [PATCH 2/2] Update packages --- .gitignore | 2 ++ src/Directory.Packages.props | 52 ++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 96895cd..a65f965 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ docker-compose.*.yml .idea +*.lscache + # User-specific files *.rsuser *.suo diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index f9b4826..05eb7a8 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -3,39 +3,39 @@ true - - + + - - + + - - - - - + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file