Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ docker-compose.*.yml

.idea

*.lscache

# User-specific files
*.rsuser
*.suo
Expand Down
52 changes: 26 additions & 26 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,39 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.0.2" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="13.0.2" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.2.2" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="13.2.2" />
<PackageVersion Include="AutoMapper" Version="[14.0.0]" />
<PackageVersion Include="FluentEmail.Core" Version="3.0.2" />
<PackageVersion Include="FluentEmail.Smtp" Version="3.0.2" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="Humanizer" Version="3.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.0" />
<PackageVersion Include="Humanizer" Version="3.0.10" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.14.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.4.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.2" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.2" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.0" />
</ItemGroup>
</Project>
8 changes: 7 additions & 1 deletion src/OpenWish.Application/Services/EventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -799,14 +799,20 @@ public async Task DeleteEventByPublicIdAsync(string publicId)
await DeleteEventAsync(existingEvent.Id);
}

public async Task<bool> AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role = "Participant")
public async Task<bool> AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role, string callerId)
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
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);
}

Expand Down
35 changes: 21 additions & 14 deletions src/OpenWish.Application/Services/NotificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@

namespace OpenWish.Application.Services;

public class NotificationService(ApplicationDbContext context, IMapper mapper) : INotificationService
public class NotificationService(IDbContextFactory<ApplicationDbContext> contextFactory, IMapper mapper) : INotificationService
{
private readonly ApplicationDbContext _context = context;
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory = contextFactory;
private readonly IMapper _mapper = mapper;

public async Task<IEnumerable<NotificationModel>> 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);

Expand All @@ -33,12 +34,14 @@ public async Task<IEnumerable<NotificationModel>> GetUserNotificationsAsync(stri

public async Task<int> 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<NotificationModel> CreateNotificationAsync(string userId, string message)
{
await using var context = await _contextFactory.CreateDbContextAsync();
var notification = new Notification
{
UserId = userId,
Expand All @@ -49,8 +52,8 @@ public async Task<NotificationModel> CreateNotificationAsync(string userId, stri
UpdatedOn = DateTimeOffset.UtcNow
};

_context.Notifications.Add(notification);
await _context.SaveChangesAsync();
context.Notifications.Add(notification);
await context.SaveChangesAsync();

return _mapper.Map<NotificationModel>(notification);
}
Expand All @@ -63,6 +66,7 @@ public async Task<NotificationModel> CreateNotificationAsync(
string type,
NotificationActionModel? action = null)
{
await using var context = await _contextFactory.CreateDbContextAsync();
var notification = new Notification
{
UserId = targetUserId,
Expand All @@ -77,15 +81,16 @@ public async Task<NotificationModel> CreateNotificationAsync(
UpdatedOn = DateTimeOffset.UtcNow
};

_context.Notifications.Add(notification);
await _context.SaveChangesAsync();
context.Notifications.Add(notification);
await context.SaveChangesAsync();

return _mapper.Map<NotificationModel>(notification);
}

public async Task<bool> 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)
{
Expand All @@ -95,13 +100,14 @@ public async Task<bool> MarkNotificationAsReadAsync(int notificationId)
notification.IsRead = true;
notification.UpdatedOn = DateTimeOffset.UtcNow;

await _context.SaveChangesAsync();
await context.SaveChangesAsync();
return true;
}

public async Task<bool> 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();

Expand All @@ -116,13 +122,14 @@ public async Task<bool> MarkAllNotificationsAsReadAsync(string userId)
notification.UpdatedOn = DateTimeOffset.UtcNow;
}

await _context.SaveChangesAsync();
await context.SaveChangesAsync();
return true;
}

public async Task<bool> 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)
{
Expand All @@ -132,7 +139,7 @@ public async Task<bool> DeleteNotificationAsync(int notificationId)
notification.Deleted = true;
notification.UpdatedOn = DateTimeOffset.UtcNow;

await _context.SaveChangesAsync();
await context.SaveChangesAsync();
return true;
}
}
12 changes: 9 additions & 3 deletions src/OpenWish.Application/Services/OpenWishEmailSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/OpenWish.Application/Services/ProductService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@ private static async Task<bool> 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;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/OpenWish.Application/Services/WishlistService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
using System.Linq;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using OpenWish.Data;
using OpenWish.Data.Entities;
using OpenWish.Shared.Models;
using OpenWish.Shared.Services;

namespace OpenWish.Application.Services;

public class WishlistService(IDbContextFactory<ApplicationDbContext> contextFactory, IMapper mapper, IActivityService activityService) : IWishlistService
public class WishlistService(IDbContextFactory<ApplicationDbContext> contextFactory, IMapper mapper, IActivityService activityService, ILogger<WishlistService> logger) : IWishlistService
{
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory = contextFactory;
private readonly IMapper _mapper = mapper;
private readonly IActivityService _activityService = activityService;
private readonly ILogger<WishlistService> _logger = logger;

public async Task<WishlistModel> CreateWishlistAsync(WishlistModel wishlistModel, string ownerId)
{
Expand All @@ -41,6 +43,8 @@ public async Task<WishlistModel> 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,
Expand Down Expand Up @@ -101,6 +105,7 @@ public async Task<WishlistModel> 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}");
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/OpenWish.Shared/Services/IEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface IEventService
Task DeleteEventAsync(int id);
Task DeleteEventByPublicIdAsync(string publicId);
Task<bool> AddUserToEventAsync(int eventId, string userId, string role = "Participant");
Task<bool> AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role = "Participant");
Task<bool> AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role, string callerId);
Task<bool> RemoveUserFromEventAsync(int eventId, string userId, string requestorId);
Task<bool> RemoveUserFromEventByPublicIdAsync(string eventPublicId, string userId, string requestorId);
Task<IEnumerable<WishlistModel>> GetEventWishlistsAsync(int eventId, string? requestingUserId = null);
Expand Down
5 changes: 2 additions & 3 deletions src/OpenWish.Web.Client/Services/ActivityHttpClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public async Task<ActivityLogModel> LogActivityAsync(
{
var activity = new
{
UserId = userId,
ActivityType = activityType,
Description = description,
WishlistId = wishlistId,
Expand All @@ -33,13 +32,13 @@ public async Task<ActivityLogModel> LogActivityAsync(

public async Task<IEnumerable<ActivityLogModel>> GetUserActivityFeedAsync(string userId, int count = 20, int skip = 0)
{
return await _httpClient.GetFromJsonAsync<IEnumerable<ActivityLogModel>>($"{BaseUrl}/user/{userId}?count={count}&skip={skip}")
return await _httpClient.GetFromJsonAsync<IEnumerable<ActivityLogModel>>($"{BaseUrl}/user?count={count}&skip={skip}")
?? Array.Empty<ActivityLogModel>();
}

public async Task<IEnumerable<ActivityLogModel>> GetFriendsActivityFeedAsync(string userId, int count = 20, int skip = 0)
{
return await _httpClient.GetFromJsonAsync<IEnumerable<ActivityLogModel>>($"{BaseUrl}/friends/{userId}?count={count}&skip={skip}")
return await _httpClient.GetFromJsonAsync<IEnumerable<ActivityLogModel>>($"{BaseUrl}/friends?count={count}&skip={skip}")
?? Array.Empty<ActivityLogModel>();
}

Expand Down
2 changes: 1 addition & 1 deletion src/OpenWish.Web.Client/Services/EventHttpClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public async Task DeleteEventByPublicIdAsync(string publicId)
response.EnsureSuccessStatusCode();
}

public async Task<bool> AddUserToEventByPublicIdAsync(string eventPublicId, string userId, string role = "Participant")
public async Task<bool> 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);
Expand Down
Loading
Loading