diff --git a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs index f3ff806..d33cf81 100644 --- a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs @@ -11,6 +11,10 @@ public class WishlistsMutation [Service] IWishlistsService wishlistsService) => wishlistsService.StartPersonalWishlistAsync(dto, cancellationToken); + public Task GenerateNameForPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.GenerateNameForPersonalWishlistAsync(wishlistId, cancellationToken); + public Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) => wishlistsService.AddMessageToPersonalWishlistAsync(wishlistId, dto, cancellationToken); diff --git a/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs index b8c6abf..cfc5e36 100644 --- a/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs +++ b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs @@ -1,9 +1,12 @@ using System.Linq.Expressions; +using MongoDB.Bson; using ShoppingAssistantApi.Domain.Entities; namespace ShoppingAssistantApi.Application.IRepositories; public interface IWishlistsRepository : IBaseRepository { - public Task GetWishlistAsync(Expression> predicate, CancellationToken cancellationToken); + Task GetWishlistAsync(Expression> predicate, CancellationToken cancellationToken); + + Task UpdateWishlistNameAsync(ObjectId id, string name, ObjectId updatedById, CancellationToken cancellationToken); } diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index d95a874..4b26cf1 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -8,6 +8,8 @@ public interface IWishlistsService { Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken); + Task GenerateNameForPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); + Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken); Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken); diff --git a/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs b/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs index a5ee8a3..e3df82c 100644 --- a/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs +++ b/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs @@ -29,9 +29,9 @@ public static class ServicesExtention "OpenAiHttpClient", client => { - client.BaseAddress = new Uri(configuration.GetValue("ApiUri")); + client.BaseAddress = new Uri(configuration.GetValue("OpenAi:ApiUrl")); client.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", configuration.GetValue("ApiKey")); + new AuthenticationHeaderValue("Bearer", configuration.GetValue("OpenAi:ApiKey")); }); return services; diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index 5dd2ae7..b6814bb 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -6,6 +6,7 @@ using ShoppingAssistantApi.Application.IRepositories; using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Application.Models.OpenAi; using ShoppingAssistantApi.Application.Paging; using ShoppingAssistantApi.Domain.Entities; using ShoppingAssistantApi.Domain.Enums; @@ -22,12 +23,16 @@ public class WishlistsService : IWishlistsService private readonly IMapper _mapper; - public WishlistsService(IWishlistsRepository wishlistRepository, IMessagesRepository messageRepository, IProductsRepository productRepository, IMapper mapper) + private readonly IOpenAiService _openAiService; + + public WishlistsService(IWishlistsRepository wishlistRepository, IMessagesRepository messageRepository, + IProductsRepository productRepository, IMapper mapper, IOpenAiService openAiService) { _wishlistsRepository = wishlistRepository; _messagesRepository = messageRepository; _productsRepository = productRepository; _mapper = mapper; + _openAiService = openAiService; } public async Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken) @@ -59,6 +64,42 @@ public class WishlistsService : IWishlistsService return _mapper.Map(createdWishlist); } + public async Task GenerateNameForPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken) + { + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + + var wishlist = await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); + + var firstUserMessage = (await _messagesRepository.GetPageAsync(1, 1, x => x.WishlistId == wishlistObjectId && x.Role == MessageRoles.User.ToString(), cancellationToken)).First(); + + var chatCompletionRequest = new ChatCompletionRequest + { + Messages = new List(2) + { + new OpenAiMessage + { + Role = OpenAiRole.System.RequestConvert(), + Content = "You will be provided with a general information about some product and your task is to generate general (not specific to any company or brand) chat name where recommendations on which specific product to buy will be given. Only name he product without adverbs and adjectives\nExamples:\n - Prompt: Hub For Macbook. Answer: Macbook Hub\n - Prompt: What is the best power bank for MacBook with capacity 20000 mAh and power near 20V? Answer: Macbook Powerbank" + }, + new OpenAiMessage + { + Role = OpenAiRole.User.RequestConvert(), + Content = firstUserMessage.Text + } + } + }; + + var openAiMessage = await _openAiService.GetChatCompletion(chatCompletionRequest, cancellationToken); + + wishlist = await _wishlistsRepository.UpdateWishlistNameAsync(wishlist.Id, + openAiMessage.Content, (ObjectId) GlobalUser.Id, cancellationToken); + + return _mapper.Map(wishlist); + } + public async Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken) { var newMessage = _mapper.Map(dto); diff --git a/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs index 04aca96..d015cf8 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using MongoDB.Bson; using MongoDB.Driver; using ShoppingAssistantApi.Application.IRepositories; using ShoppingAssistantApi.Domain.Entities; @@ -14,4 +15,22 @@ public class WishlistsRepository : BaseRepository, IWishlistsRepositor { return await (await _collection.FindAsync(predicate)).FirstOrDefaultAsync(cancellationToken); } + + public async Task UpdateWishlistNameAsync(ObjectId wishlistId, string newName, + ObjectId updatedById, CancellationToken cancellationToken) + { + var filterDefinition = Builders.Filter.Eq(w => w.Id, wishlistId); + + var updateDefinition = Builders.Update + .Set(w => w.Name, newName) + .Set(w => w.LastModifiedDateUtc, DateTime.UtcNow) + .Set(w => w.LastModifiedById, updatedById); + + var options = new FindOneAndUpdateOptions + { + ReturnDocument = ReturnDocument.After + }; + + return await _collection.FindOneAndUpdateAsync(filterDefinition, updateDefinition, options, cancellationToken); + } } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index 5d989b7..6a3c3a0 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -61,6 +61,58 @@ public class WishlistsTests : TestsBase Assert.Equal($"{WishlistTypes.Product} Search", wishlist.Name); } + [Fact] + public async Task GenerateNameForPersonalWishlist_ValidWishlistId_ReturnsNewName() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var startWishlistMutation = new + { + query = @" + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist (dto: $dto) { + id, name, type, createdById + } + }", + variables = new + { + dto = new + { + firstMessageText = "Mechanical keyboard for programming", + type = WishlistTypes.Product.ToString() + } + } + }; + + var jsonObject = await SendGraphQlRequestAsync(startWishlistMutation); + var startWishlistResponse = (WishlistDto?) jsonObject?.data?.startPersonalWishlist?.ToObject(); + + Assert.NotNull(startWishlistResponse); + + var generateWishlistNameMutation = new + { + query = @" + mutation genarateNameForPersonalWishlist($wishlistId: String!) { + generateNameForPersonalWishlist(wishlistId: $wishlistId) { + id, name, type, createdById + } + }", + variables = new + { + wishlistId = startWishlistResponse.Id + } + }; + + jsonObject = await SendGraphQlRequestAsync(generateWishlistNameMutation); + var generateWishlistNameResponse = (WishlistDto?) jsonObject?.data?.generateNameForPersonalWishlist?.ToObject(); + + Assert.NotNull(generateWishlistNameResponse); + Assert.Equal(startWishlistResponse.Id, generateWishlistNameResponse.Id); + + Assert.NotEqual($"{startWishlistResponse.Type} Search", generateWishlistNameResponse.Name); + Assert.NotEqual(String.Empty, generateWishlistNameResponse.Name); + Assert.NotEqual(null, generateWishlistNameResponse.Name); + } + [Fact] public async Task GetPersonalWishlistsPage_ValidPageNumberAndSize_ReturnsPage() {