From e0cbf86fc80747d9905ebc1900c97b627377bd77 Mon Sep 17 00:00:00 2001 From: ConfuzzedCat Date: Mon, 6 Apr 2026 01:39:56 +0200 Subject: [PATCH] WIP, fix wish.price. Added Wish, Wishlist, services, data classes --- Wishlist/Data/AppDataContext.cs | 18 ++ Wishlist/Data/Constants.cs | 3 + Wishlist/Data/Entities/CurrencyInfo.cs | 38 ++++ Wishlist/Data/Entities/PriceInfo.cs | 24 +++ Wishlist/Data/Entities/ReserveStatus.cs | 23 +++ Wishlist/Data/Entities/User.cs | 1 + Wishlist/Data/Entities/VisibilityStatus.cs | 31 +++ Wishlist/Data/Entities/Wish.cs | 35 ++++ Wishlist/Data/Entities/Wishlist.cs | 28 +++ Wishlist/Data/Services/Interfaces/ICrud.cs | 12 ++ .../Data/Services/Interfaces/IWishService.cs | 17 ++ .../Services/Interfaces/IWishlistService.cs | 9 + Wishlist/Data/Services/WishService.cs | 176 ++++++++++++++++++ Wishlist/Data/Services/WishlistService.cs | 86 +++++++++ Wishlist/Program.cs | 4 + Wishlist/Wishlist.csproj | 5 + 16 files changed, 510 insertions(+) create mode 100644 Wishlist/Data/Entities/CurrencyInfo.cs create mode 100644 Wishlist/Data/Entities/PriceInfo.cs create mode 100644 Wishlist/Data/Entities/ReserveStatus.cs create mode 100644 Wishlist/Data/Entities/VisibilityStatus.cs create mode 100644 Wishlist/Data/Entities/Wish.cs create mode 100644 Wishlist/Data/Entities/Wishlist.cs create mode 100644 Wishlist/Data/Services/Interfaces/ICrud.cs create mode 100644 Wishlist/Data/Services/Interfaces/IWishService.cs create mode 100644 Wishlist/Data/Services/Interfaces/IWishlistService.cs create mode 100644 Wishlist/Data/Services/WishService.cs create mode 100644 Wishlist/Data/Services/WishlistService.cs diff --git a/Wishlist/Data/AppDataContext.cs b/Wishlist/Data/AppDataContext.cs index b311b90..5ce0787 100644 --- a/Wishlist/Data/AppDataContext.cs +++ b/Wishlist/Data/AppDataContext.cs @@ -6,8 +6,26 @@ namespace Wishlist.Data; public class AppDataContext : IdentityDbContext { + + public DbSet Wishlists { get; set; } + public DbSet Wishes { get; set; } public AppDataContext() { } public AppDataContext(DbContextOptions options) : base(options) { } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.Entity() + .HasMany(e => e.Wishes) + .WithOne(e => e.Wishlist) + .HasForeignKey(e => e.WishlistId) + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + builder.Entity() + .HasMany(e => e.Wishlists) + .WithMany(e => e.Owners) + .UsingEntity(j => j.ToTable("OwnerWishlists")); + } } \ No newline at end of file diff --git a/Wishlist/Data/Constants.cs b/Wishlist/Data/Constants.cs index 6d98f3c..a84a5df 100644 --- a/Wishlist/Data/Constants.cs +++ b/Wishlist/Data/Constants.cs @@ -1,3 +1,5 @@ +using Wishlist.Data.Entities; + namespace Wishlist.Data; public class Constants @@ -7,4 +9,5 @@ public class Constants public const string MemberRole = "Member"; // Static Readonly (Runtime const) public static readonly string[] Roles = [AdminRole, MemberRole]; + public static readonly CurrencyInfo DefaultCurrency = CurrencyInfo.DKK; } \ No newline at end of file diff --git a/Wishlist/Data/Entities/CurrencyInfo.cs b/Wishlist/Data/Entities/CurrencyInfo.cs new file mode 100644 index 0000000..5dc946f --- /dev/null +++ b/Wishlist/Data/Entities/CurrencyInfo.cs @@ -0,0 +1,38 @@ +namespace Wishlist.Data.Entities; + +public struct CurrencyInfo +{ + public CurrencyInfo() + { + CurrencyCode = string.Empty; + CurrencyName = string.Empty; + Symbol = string.Empty; + } + + // ReSharper disable InconsistentNaming + public static readonly CurrencyInfo DKK = new() + { + CurrencyCode = "DKK", + CurrencyName = "Danish Crowns", + Symbol = "Kr", + }; + public static readonly CurrencyInfo EURO = new() + { + CurrencyCode = "EURO", + CurrencyName = "Euros", + Symbol = "€" + }; + public static readonly CurrencyInfo USD = new() + { + CurrencyCode = "USD", + CurrencyName = "US Dollars", + Symbol = "$" + }; + // ReSharper restore InconsistentNaming + + + + public string CurrencyCode { get; set; } + public string CurrencyName { get; set; } + public string Symbol { get; set; } +} \ No newline at end of file diff --git a/Wishlist/Data/Entities/PriceInfo.cs b/Wishlist/Data/Entities/PriceInfo.cs new file mode 100644 index 0000000..830eea9 --- /dev/null +++ b/Wishlist/Data/Entities/PriceInfo.cs @@ -0,0 +1,24 @@ +namespace Wishlist.Data.Entities; + +public struct PriceInfo +{ + public double Price { get; set; } + public CurrencyInfo CurrencyInfo { get; set; } + + public PriceInfo() + { + Price = 0; + CurrencyInfo = new CurrencyInfo(); + } + + public PriceInfo(double price) + { + Price = price; + CurrencyInfo = new CurrencyInfo(); + } + public PriceInfo(double price, CurrencyInfo currencyInfo) + { + Price = price; + CurrencyInfo = currencyInfo; + } +} \ No newline at end of file diff --git a/Wishlist/Data/Entities/ReserveStatus.cs b/Wishlist/Data/Entities/ReserveStatus.cs new file mode 100644 index 0000000..ac8ec64 --- /dev/null +++ b/Wishlist/Data/Entities/ReserveStatus.cs @@ -0,0 +1,23 @@ +namespace Wishlist.Data.Entities; + +public struct ReserveStatus +{ + public User? ReserveUser { get; set; } + + public static readonly ReserveStatus NotReserve = new (); + + public ReserveStatus() + { + ReserveUser = null; + } + + public ReserveStatus(User? reserveUser) + { + ReserveUser = reserveUser; + } + + public bool IsReserved() + { + return ReserveUser is not null; + } +} \ No newline at end of file diff --git a/Wishlist/Data/Entities/User.cs b/Wishlist/Data/Entities/User.cs index 255606b..9d3df0c 100644 --- a/Wishlist/Data/Entities/User.cs +++ b/Wishlist/Data/Entities/User.cs @@ -4,5 +4,6 @@ namespace Wishlist.Data.Entities; public class User : IdentityUser { + public List Wishlists { get; set; } } \ No newline at end of file diff --git a/Wishlist/Data/Entities/VisibilityStatus.cs b/Wishlist/Data/Entities/VisibilityStatus.cs new file mode 100644 index 0000000..c36c251 --- /dev/null +++ b/Wishlist/Data/Entities/VisibilityStatus.cs @@ -0,0 +1,31 @@ +namespace Wishlist.Data.Entities; + +public struct VisibilityStatus +{ + public VisibilityStatus() { } + + public List VisibleTo { get; set; } = []; + public bool Published { get; set; } + public bool Public { get; set; } + + public bool IsVisibleForUSer(User user) + { + return Public || Published && VisibleTo.Contains(user); + } + + public void AddUser(User user) + { + VisibleTo.Add(user); + } + + public void RemoveUser(User user) + { + VisibleTo.Remove(user); + } + + public bool IsEmpty() + { + return VisibleTo.Count == 0; + } + +} \ No newline at end of file diff --git a/Wishlist/Data/Entities/Wish.cs b/Wishlist/Data/Entities/Wish.cs new file mode 100644 index 0000000..213503c --- /dev/null +++ b/Wishlist/Data/Entities/Wish.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; + +namespace Wishlist.Data.Entities; + +public class Wish +{ + public Guid Id { get; set; } + [MaxLength(50)] + public string Name { get; set; } + [MaxLength(2000)] + public string Description { get; set; } + public DateTime Created { get; set; } + public DateTime LastModified { get; set; } + public bool IsVisible { get; set; } + public ReserveStatus ReserveStatus { get; set; } + public PriceInfo Price { get; set; } + public Uri ImageUrl { get; set; } + public Uri StoreLink { get; set; } + public Guid WishlistId { get; set; } + public Wishlist Wishlist { get; set; } = null!; + + public Wish() + { + Id = Guid.NewGuid(); + Created = DateTime.UtcNow; + LastModified = DateTime.UtcNow; + IsVisible = false; + ReserveStatus = ReserveStatus.NotReserve; + Price = new(0, Constants.DefaultCurrency); + ImageUrl = new Uri(string.Empty); + StoreLink = new Uri(string.Empty); + Description = string.Empty; + Name = string.Empty; + } +} \ No newline at end of file diff --git a/Wishlist/Data/Entities/Wishlist.cs b/Wishlist/Data/Entities/Wishlist.cs new file mode 100644 index 0000000..26d196d --- /dev/null +++ b/Wishlist/Data/Entities/Wishlist.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace Wishlist.Data.Entities; + +public class Wishlist +{ + public Guid Id { get; set; } + [MaxLength(50)] + public string Title { get; set; } + [MaxLength(2000)] + public string Description { get; set; } + public List Wishes { get; set; } + public List Owners { get; set; } + public VisibilityStatus VisibilityStatus { get; set; } + public DateTime Created { get; set; } + public DateTime LastModified { get; set; } + + public Wishlist() + { + Id = Guid.NewGuid(); + Title = string.Empty; + Description = string.Empty; + Wishes = []; + VisibilityStatus = new VisibilityStatus(); + Created = DateTime.UtcNow; + LastModified = DateTime.UtcNow; + } +} \ No newline at end of file diff --git a/Wishlist/Data/Services/Interfaces/ICrud.cs b/Wishlist/Data/Services/Interfaces/ICrud.cs new file mode 100644 index 0000000..f3af27c --- /dev/null +++ b/Wishlist/Data/Services/Interfaces/ICrud.cs @@ -0,0 +1,12 @@ +using CSharpFunctionalExtensions; + +namespace Wishlist.Data.Services.Interfaces; + + +public interface ICrud +{ + Task> Create(TEntity entity); + Task> Read(TKey entityId); + Task> Update(TEntity entity); + Task> Delete(TKey entityId); +} \ No newline at end of file diff --git a/Wishlist/Data/Services/Interfaces/IWishService.cs b/Wishlist/Data/Services/Interfaces/IWishService.cs new file mode 100644 index 0000000..7c3acf9 --- /dev/null +++ b/Wishlist/Data/Services/Interfaces/IWishService.cs @@ -0,0 +1,17 @@ +using CSharpFunctionalExtensions; +using Wishlist.Data.Entities; + +namespace Wishlist.Data.Services.Interfaces; + +public interface IWishService : ICrud +{ + Task>> GetAllWishes(Guid listId); + Task> UpdateReservedStatus(Guid wishId, User? reservedBy); + Task> UpdatePriceInfo(Guid wishId, PriceInfo priceInfo); + Task> UpdateImage(Guid wishId, Uri imageUri); + Task> UpdateStore(Guid wishId, Uri storeUri); + Task> UpdateName(Guid wishId, string name); + Task> UpdateDescription(Guid wishId, string description); + Task> UpdateVisibility(Guid wishId, bool isVisible); + Task> UpdatePrice(Guid wishId, double price); +} \ No newline at end of file diff --git a/Wishlist/Data/Services/Interfaces/IWishlistService.cs b/Wishlist/Data/Services/Interfaces/IWishlistService.cs new file mode 100644 index 0000000..1b2d8e3 --- /dev/null +++ b/Wishlist/Data/Services/Interfaces/IWishlistService.cs @@ -0,0 +1,9 @@ +using CSharpFunctionalExtensions; +using Wishlist.Data.Entities; + +namespace Wishlist.Data.Services.Interfaces; + +public interface IWishlistService : ICrud +{ + Task> AddWish(Guid id, Wish wish); +} \ No newline at end of file diff --git a/Wishlist/Data/Services/WishService.cs b/Wishlist/Data/Services/WishService.cs new file mode 100644 index 0000000..3f2dd02 --- /dev/null +++ b/Wishlist/Data/Services/WishService.cs @@ -0,0 +1,176 @@ +using CSharpFunctionalExtensions; +using Wishlist.Data.Entities; +using Wishlist.Data.Services.Interfaces; + +namespace Wishlist.Data.Services; + +public class WishService : IWishService +{ + private readonly AppDataContext _context; + private readonly ILogger _logger; + private readonly IWishlistService _wishlistService; + + public WishService(AppDataContext context, ILogger logger, IWishlistService wishlistService) + { + _context = context; + _logger = logger; + _wishlistService = wishlistService; + } + + public async Task> Create(Wish entity) + { + try + { + _logger.LogInformation($"Creating Wish {entity.Id}"); + entity.Created = DateTime.UtcNow; + entity.LastModified = entity.Created; + var createdEntity = await _context.Wishes.AddAsync(entity); + await _context.SaveChangesAsync(); + return createdEntity.Entity; + } + catch (Exception e) + { + _logger.LogError("An error occurred while creating the Wish. {Entity} - {Error}", entity, e.Message); + return Result.Failure($"An error occurred while creating the Wish. {e}"); + } + } + + public async Task> Read(Guid entityId) + { + var entity = await _context.Wishes.FindAsync(entityId); + return entity; + } + + public async Task> Update(Wish entity) + { + var oldEntity = await Read(entity.Id); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + entity.LastModified = DateTime.UtcNow; + _context.Wishes.Update(entity); + await _context.SaveChangesAsync(); + return Result.Success(entity); + } + + public async Task> Delete(Guid entityId) + { + var entity = await Read(entityId); + if (entity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var deletedEntity = _context.Wishes.Remove(entity.Value); + await _context.SaveChangesAsync(); + return Result.Success(deletedEntity.Entity); + } + + public async Task>> GetAllWishes(Guid listId) + { + var listMaybe = await _wishlistService.Read(listId); + if (listMaybe.HasNoValue) + { + return Result.Failure>("The given list was not found."); + } + return listMaybe.Value.Wishes; + } + + public async Task> UpdateReservedStatus(Guid wishId, User? reservedBy) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.ReserveStatus = new ReserveStatus(reservedBy); + return await Update(wish); + } + + public async Task> UpdatePriceInfo(Guid wishId, PriceInfo priceInfo) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.Price = priceInfo; + return await Update(wish); + } + + public async Task> UpdateImage(Guid wishId, Uri imageUri) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.ImageUrl = imageUri; + return await Update(wish); + } + + public async Task> UpdateStore(Guid wishId, Uri storeUri) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.StoreLink = storeUri; + return await Update(wish); + } + + public async Task> UpdateName(Guid wishId, string name) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.Name = name; + return await Update(wish); + } + + public async Task> UpdateDescription(Guid wishId, string description) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.Description = description; + return await Update(wish); + } + + public async Task> UpdateVisibility(Guid wishId, bool isVisible) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + wish.IsVisible = isVisible; + return await Update(wish); + } + + public async Task> UpdatePrice(Guid wishId, double price) + { + var oldEntity = await Read(wishId); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var wish = oldEntity.Value; + var oldPrice = wish.Price; + oldPrice.Price = price; + wish.Price = oldPrice; + return await Update(wish); + } +} \ No newline at end of file diff --git a/Wishlist/Data/Services/WishlistService.cs b/Wishlist/Data/Services/WishlistService.cs new file mode 100644 index 0000000..a6bfc9c --- /dev/null +++ b/Wishlist/Data/Services/WishlistService.cs @@ -0,0 +1,86 @@ +using CSharpFunctionalExtensions; +using Wishlist.Data.Entities; +using Wishlist.Data.Services.Interfaces; + +namespace Wishlist.Data.Services; + +public class WishlistService : IWishlistService +{ + private readonly AppDataContext _context; + private readonly ILogger _logger; + + public WishlistService(AppDataContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task> Create(Entities.Wishlist entity) + { + try + { + _logger.LogInformation($"Creating Wishlist {entity.Id}"); + entity.Created = DateTime.UtcNow; + entity.LastModified = entity.Created; + var createdEntity = await _context.Wishlists.AddAsync(entity); + await _context.SaveChangesAsync(); + return createdEntity.Entity; + } + catch (Exception e) + { + _logger.LogError("An error occurred while creating the Wishlist. {Entity} - {Error}", entity, e.Message); + return Result.Failure($"An error occurred while creating the Wishlist. {e}"); + } + } + + public async Task> Read(Guid entityId) + { + var entity = await _context.Wishlists.FindAsync(entityId); + return entity; + } + + public async Task> Update(Entities.Wishlist entity) + { + var oldEntity = await Read(entity.Id); + if (oldEntity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + entity.LastModified = DateTime.UtcNow; + _context.Wishlists.Update(entity); + await _context.SaveChangesAsync(); + return Result.Success(entity); + } + + public async Task> Delete(Guid entityId) + { + var entity = await Read(entityId); + if (entity.HasNoValue) + { + return Result.Failure("The given entity was not found."); + } + var deletedEntity = _context.Wishlists.Remove(entity.Value); + await _context.SaveChangesAsync(); + return Result.Success(deletedEntity.Entity); + } + + public async Task> AddWish(Guid id, Wish wish) + { + var listMaybe = await Read(id); + if (listMaybe.HasNoValue) + { + return Result.Failure($"Wishlist '{id}' was not found. Wish '{wish.Id}' not added"); + } + var wishlist = listMaybe.Value; + + wish.WishlistId = wishlist.Id; + wishlist.Wishes.Add(wish); + var update = await Update(wishlist); + if (update.IsFailure) + { + return Result.Failure(update.Error); + } + await _context.SaveChangesAsync(); + return Result.Success(wish); + } +} \ No newline at end of file diff --git a/Wishlist/Program.cs b/Wishlist/Program.cs index 1ed0eda..abcba56 100644 --- a/Wishlist/Program.cs +++ b/Wishlist/Program.cs @@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore; using Wishlist.Components; using Wishlist.Data; using Wishlist.Data.Entities; +using Wishlist.Data.Services; +using Wishlist.Data.Services.Interfaces; namespace Wishlist; @@ -26,6 +28,8 @@ public class Program builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddAuthentication(options => { diff --git a/Wishlist/Wishlist.csproj b/Wishlist/Wishlist.csproj index 2ef5876..43af255 100644 --- a/Wishlist/Wishlist.csproj +++ b/Wishlist/Wishlist.csproj @@ -9,6 +9,11 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +