diff --git a/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.review.json b/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.review.json
new file mode 100644
index 00000000..89a89b2b
--- /dev/null
+++ b/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.review.json
@@ -0,0 +1,26 @@
+[
+ {
+ "UpstreamPathTemplate": "/api/review/{everything}",
+ "UpstreamHttpMethod": ["GET"],
+ "DownstreamPathTemplate": "/api/review/{everything}",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "review_service",
+ "Port": 8080
+ }
+ ]
+ },
+ {
+ "UpstreamPathTemplate": "/api/review/{everything}",
+ "UpstreamHttpMethod": ["POST", "PUT", "DELETE"],
+ "DownstreamPathTemplate": "/api/review/{everything}",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "review_service",
+ "Port": 8080
+ }
+ ]
+ }
+]
diff --git a/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.swagger.json b/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.swagger.json
index 2e43942a..209345ec 100644
--- a/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.swagger.json
+++ b/apps/merchant-api/ApiGateway/RoutingConfiguration/ocelot.swagger.json
@@ -46,5 +46,17 @@
],
"UpstreamPathTemplate": "/swagger/notification",
"UpstreamHttpMethod": ["GET"]
+ },
+ {
+ "DownstreamPathTemplate": "/swagger/v1/swagger.json",
+ "DownstreamScheme": "http",
+ "DownstreamHostAndPorts": [
+ {
+ "Host": "review_service",
+ "Port": 8080
+ }
+ ],
+ "UpstreamPathTemplate": "/swagger/review",
+ "UpstreamHttpMethod": ["GET"]
}
]
diff --git a/apps/merchant-api/ApiGateway/ServerConfigurations/SwaggerConfiguration.cs b/apps/merchant-api/ApiGateway/ServerConfigurations/SwaggerConfiguration.cs
index 09e9cca6..bad8a5e6 100644
--- a/apps/merchant-api/ApiGateway/ServerConfigurations/SwaggerConfiguration.cs
+++ b/apps/merchant-api/ApiGateway/ServerConfigurations/SwaggerConfiguration.cs
@@ -12,6 +12,8 @@ public static void ConfigureSwagger(this IServiceCollection services)
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Inventory Service", Version = "v1" });
c.SwaggerDoc("v2", new OpenApiInfo { Title = "User Service", Version = "v1" });
c.SwaggerDoc("v3", new OpenApiInfo { Title = "Payment Service", Version = "v1" });
+ c.SwaggerDoc("v4", new OpenApiInfo { Title = "Notification Service", Version = "v1" });
+ c.SwaggerDoc("v5", new OpenApiInfo { Title = "Review Service", Version = "v1" });
});
}
@@ -24,6 +26,7 @@ public static void UseSwaggerConfig(this WebApplication app)
c.SwaggerEndpoint("http://localhost:5001/swagger/user", "User Service V1");
c.SwaggerEndpoint("http://localhost:5001/swagger/payment", "Payment Service V1");
c.SwaggerEndpoint("http://localhost:5001/swagger/notification", "Notification Service V1");
+ c.SwaggerEndpoint("http://localhost:5001/swagger/review", "Review Service V1");
c.RoutePrefix = string.Empty;
});
}
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Api/Program.cs b/apps/merchant-api/InventoryService/src/InventoryService.Api/Program.cs
index 030c03ad..8815a05e 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Api/Program.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Api/Program.cs
@@ -1,14 +1,16 @@
using InventoryService.Api;
using DotNetEnv;
+using InventoryService.Api.Controllers;
using InventoryService.Intraestructure.Data;
using InventoryService.Application;
using InventoryService.Application.Profiles;
using InventoryService.Application.ValidatorSettings;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
+using RabbitMQMessaging.Extensions;
-var builder = WebApplication.CreateBuilder(args);
Env.Load("../../../.env");
+var builder = WebApplication.CreateBuilder(args);
var apiGatewayUrl = builder.Configuration["ApiGatewayUrl"] ?? "http://localhost:5001";
builder.Services.AddCors(options =>
@@ -31,6 +33,7 @@
builder.Services.AddMediatR(cfg=>cfg.RegisterServicesFromAssemblies(typeof(ProductProfile).Assembly));
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
+builder.Services.AddMassTransitWithRabbitMq("inventory", typeof(CategoryController));
var connectionString = builder.Configuration["POSTGRES_SQL_CONNECTION"] ?? throw new ArgumentNullException("POSTGRES_SQL_CONNECTION environment variable is not set.");
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Api/QueueHandlers/RatingUpdateHandler.cs b/apps/merchant-api/InventoryService/src/InventoryService.Api/QueueHandlers/RatingUpdateHandler.cs
new file mode 100644
index 00000000..e07c0855
--- /dev/null
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Api/QueueHandlers/RatingUpdateHandler.cs
@@ -0,0 +1,14 @@
+using Commons.Messages;
+using InventoryService.Intraestructure.Repositories.Interfaces;
+using MassTransit;
+
+namespace InventoryService.Api.QueueHandlers;
+
+public class RatingUpdateHandler(IProductRepository repository) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ var ratingUpdated = context.Message;
+ await repository.UpdateRating(ratingUpdated.ProductId, ratingUpdated.Rating);
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Application/Dtos/Products/ProductDto.cs b/apps/merchant-api/InventoryService/src/InventoryService.Application/Dtos/Products/ProductDto.cs
index 1787934a..6ed83489 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Application/Dtos/Products/ProductDto.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Application/Dtos/Products/ProductDto.cs
@@ -12,6 +12,7 @@ public class ProductDto
public bool IsLiked { get; set; }
public string Brand { get; set; } = string.Empty;
public int? LowStockThreshold { get; set; }
+ public float Rating { get; set; }
public List Categories { get; set; } = [];
public List Images { get; set; } = [];
public List ProductVariants { get; set; } = [];
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Application/Services/ProductService.cs b/apps/merchant-api/InventoryService/src/InventoryService.Application/Services/ProductService.cs
index c44bb515..275d79e8 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Application/Services/ProductService.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Application/Services/ProductService.cs
@@ -22,6 +22,7 @@ public ProductDto GetProductDtoByProduct(Product existingProduct, List pro
IsLiked = isLiked,
Price = existingProduct.BasePrice,
Brand = existingProduct.Brand,
+ Rating = existingProduct.Rating,
Categories = categoriesDto,
Images = existingProduct.Images.Where(i => i.IsActive).Select(i => new ProductVariantImageDto
{
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Domain/Concretes/Product.cs b/apps/merchant-api/InventoryService/src/InventoryService.Domain/Concretes/Product.cs
index 6e75630e..89e45de0 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Domain/Concretes/Product.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Domain/Concretes/Product.cs
@@ -10,6 +10,7 @@ public class Product : BaseEntity
public double BasePrice { get; set; }
public string Brand { get; set; } = string.Empty;
public int? LowStockThreshold { get; set; }
+ public float Rating { get; set; } = 0;
public ICollection Images { get; set; } = new List();
public ICollection ProductVariants { get; set; } = new List();
public ICollection Categories { get; set; } = new List();
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Concretes/ProductRepository.cs b/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Concretes/ProductRepository.cs
index ba612a31..0bcddc60 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Concretes/ProductRepository.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Concretes/ProductRepository.cs
@@ -111,4 +111,14 @@ public async Task GetCountProductsByStoreId(Guid id, ProductFilteringQueryP
}
return await query.CountAsync();
}
+
+ public async Task UpdateRating(Guid id, int rating)
+ {
+ var product = await GetByIdAsync(id);
+ if (product == null) return false;
+
+ product.Rating = rating;
+ var response = await UpdateAsync(product);
+ return true;
+ }
}
\ No newline at end of file
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Interfaces/IProductRepository.cs b/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Interfaces/IProductRepository.cs
index 0191f67d..d90a27dc 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Interfaces/IProductRepository.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Interfaces/IProductRepository.cs
@@ -6,4 +6,5 @@ public interface IProductRepository : IRepository
{
Task> GetProductsByStoreId(Guid id, int pageNumber, int pageSize, ProductFilteringQueryParams queryParams);
Task GetCountProductsByStoreId(Guid id, ProductFilteringQueryParams queryParams);
+ Task UpdateRating(Guid id, float rating);
}
\ No newline at end of file
diff --git a/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Utils/ProductFiltersManager.cs b/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Utils/ProductFiltersManager.cs
index 54a07371..6a1c78eb 100644
--- a/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Utils/ProductFiltersManager.cs
+++ b/apps/merchant-api/InventoryService/src/InventoryService.Intraestructure/Repositories/Utils/ProductFiltersManager.cs
@@ -53,12 +53,12 @@ private async Task>>> _GetFilters(ProductFil
if (queryParams.MinRating.HasValue)
{
- // TODO: Implement filter support.
+ predicates.Add(p => p.Rating >= queryParams.MinRating.Value);
}
if (queryParams.MaxRating.HasValue)
{
- // TODO: Implement filter support.
+ predicates.Add(p => p.Rating <= queryParams.MaxRating.Value);
}
return predicates;
@@ -91,7 +91,14 @@ public IQueryable _ApplySort(IQueryable query, ProductFilterin
if (queryParams.RatingAsc.HasValue)
{
- // TODO: Add support for rating sort.
+ query = (bool)queryParams.RatingAsc
+ ? someSortApplied
+ ? ((IOrderedQueryable)query).ThenBy(p => p.Rating)
+ : query.OrderBy(p => p.Rating)
+ : someSortApplied
+ ? ((IOrderedQueryable)query).ThenByDescending(p => p.Rating)
+ : query.OrderByDescending(p => p.Rating);
+ someSortApplied = true;
}
if (!someSortApplied)
diff --git a/apps/merchant-api/MerchantCommon/Commons/Dto/PaginatedResponseDto.cs b/apps/merchant-api/MerchantCommon/Commons/Dto/PaginatedResponseDto.cs
new file mode 100644
index 00000000..1f34a7f6
--- /dev/null
+++ b/apps/merchant-api/MerchantCommon/Commons/Dto/PaginatedResponseDto.cs
@@ -0,0 +1,11 @@
+namespace Commons.Dto;
+
+public class PaginatedResponseDto(List items, int totalItems, int page, int pageSize)
+{
+ public List? Items { get; set; } = items;
+ public int TotalCount { get; set; } = items.Count;
+ public int Page { get; set; } = page;
+ public int PageSize { get; set; } = pageSize;
+ public int TotalItems { get; set; } = totalItems;
+ public int TotalPages { get; set; } = (int)Math.Ceiling(totalItems / (double)pageSize);
+}
\ No newline at end of file
diff --git a/apps/merchant-api/MerchantCommon/Commons/Messages/RatingMessage.cs b/apps/merchant-api/MerchantCommon/Commons/Messages/RatingMessage.cs
new file mode 100644
index 00000000..4e1c35a9
--- /dev/null
+++ b/apps/merchant-api/MerchantCommon/Commons/Messages/RatingMessage.cs
@@ -0,0 +1,7 @@
+namespace Commons.Messages;
+
+public class RatingMessage
+{
+ public required Guid ProductId { get; set; }
+ public required float Rating { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/MerchantCommon/RabbitMqMessaging/Extensions/AddRabbitMqConfiguration.cs b/apps/merchant-api/MerchantCommon/RabbitMqMessaging/Extensions/AddRabbitMqConfiguration.cs
index 612267e0..26985318 100644
--- a/apps/merchant-api/MerchantCommon/RabbitMqMessaging/Extensions/AddRabbitMqConfiguration.cs
+++ b/apps/merchant-api/MerchantCommon/RabbitMqMessaging/Extensions/AddRabbitMqConfiguration.cs
@@ -9,7 +9,6 @@ public static class Extensions
{
public static IServiceCollection AddMassTransitWithRabbitMq(this IServiceCollection services, string serviceName, Type? type=null)
{
-
var rabbitMqConnectionString = Environment.GetEnvironmentVariable("RABBITMQ_CONNECTION_STRING");
var rabbitMqUser = Environment.GetEnvironmentVariable("RABBITMQ_USER") ?? throw new("RABBITMQ_USER");
var rabbitMqPass = Environment.GetEnvironmentVariable("RABBITMQ_PASS") ?? throw new("RABBITMQ_PASS");
diff --git a/apps/merchant-api/ReviewService/Dockerfile b/apps/merchant-api/ReviewService/Dockerfile
new file mode 100644
index 00000000..eb61ee80
--- /dev/null
+++ b/apps/merchant-api/ReviewService/Dockerfile
@@ -0,0 +1,29 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /app
+
+COPY ReviewService/src/ReviewService.Api/ReviewService.Api.csproj ReviewService/src/ReviewService.Api/
+COPY ReviewService/src/ReviewService.Application/ReviewService.Application.csproj ReviewService/src/ReviewService.Application/
+COPY ReviewService/src/ReviewService.Domain/ReviewService.Domain.csproj ReviewService/src/ReviewService.Domain/
+COPY ReviewService/src/ReviewService.Infrastructure/ReviewService.Infrastructure.csproj ReviewService/src/ReviewService.Infrastructure/
+COPY MerchantCommon/RabbitMqMessaging/RabbitMqMessaging.csproj MerchantCommon/RabbitMqMessaging/
+COPY NotificationService/src/NotificationService.Domain/NotificationService.Domain.csproj NotificationService/src/NotificationService.Domain/
+COPY MerchantCommon/Commons/Commons.csproj MerchantCommon/Commons/
+
+WORKDIR /app/ReviewService/src/ReviewService.Api
+RUN dotnet restore
+
+COPY ReviewService/src/ReviewService.Api/. .
+COPY ReviewService/src/ReviewService.Application/. ../ReviewService.Application/
+COPY ReviewService/src/ReviewService.Domain/. ../ReviewService.Domain/
+COPY ReviewService/src/ReviewService.Infrastructure/. ../ReviewService.Infrastructure/
+COPY NotificationService/src/NotificationService.Domain/. /app/NotificationService/src/NotificationService.Domain/
+COPY MerchantCommon/RabbitMqMessaging/. /app/MerchantCommon/RabbitMqMessaging/
+COPY MerchantCommon/Commons/. /app/MerchantCommon/Commons/
+
+RUN dotnet publish -c Release -o /app/out
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0
+WORKDIR /app
+COPY --from=build /app/out .
+EXPOSE 8080
+ENTRYPOINT ["dotnet", "ReviewService.Api.dll"]
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/Configuration/ConfigDb.cs b/apps/merchant-api/ReviewService/src/ReviewService.Api/Configuration/ConfigDb.cs
new file mode 100644
index 00000000..52686ec5
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/Configuration/ConfigDb.cs
@@ -0,0 +1,25 @@
+using MongoDB.Driver;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Context.Concretes;
+using ReviewService.Infrastructure.Context.Interfaces;
+
+namespace ReviewService.Api.Configuration;
+
+public static class ConfigDb
+{
+ public static IServiceCollection ConfigDbSet(this IServiceCollection services, string connectionString)
+ {
+ var client = new MongoClient(connectionString);
+ var db = client.GetDatabase("reviews_db");
+
+ services.AddScoped(_ => db);
+ services.ConfigContext();
+ return services;
+ }
+
+ public static IServiceCollection ConfigContext(this IServiceCollection services)
+ {
+ services.AddScoped, ProductReviewContext>();
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/Configuration/ConfigRepositories.cs b/apps/merchant-api/ReviewService/src/ReviewService.Api/Configuration/ConfigRepositories.cs
new file mode 100644
index 00000000..4f07f368
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/Configuration/ConfigRepositories.cs
@@ -0,0 +1,13 @@
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Repositories.Concretes;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+
+namespace ReviewService.Api.Configuration;
+
+public static class ConfigRepositories
+{
+ public static IServiceCollection AddRepositories(this IServiceCollection services)
+ {
+ return services.AddScoped, ProductReviewRepository>();
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/Controllers/ProductReviewController.cs b/apps/merchant-api/ReviewService/src/ReviewService.Api/Controllers/ProductReviewController.cs
new file mode 100644
index 00000000..07f4683a
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/Controllers/ProductReviewController.cs
@@ -0,0 +1,69 @@
+using Commons.ResponseHandler.Responses.Concretes;
+
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using ReviewService.Application.CommandsQueries.Commons;
+using ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+using ReviewService.Application.CommandsQueries.ProductReview.Queries.Requests;
+using ReviewService.Application.Dtos.ProductReview;
+using ReviewService.Application.Dtos.Review;
+using ReviewService.Concretes;
+
+namespace ReviewService.Api.Controllers;
+
+[ApiController]
+[Route("api/review/[controller]")]
+public class ProductReviewController(IMediator mediator) : ControllerBase
+{
+ [HttpPost]
+ public async Task Create(CreateProductReviewDto productReview)
+ {
+ var result = await mediator.Send(new CreateProductReview(productReview));
+ if (result is ErrorResponse errorResponse)
+ return StatusCode(errorResponse.StatusCode, errorResponse);
+ var successResponse = (SuccessResponse)result;
+ return StatusCode(successResponse.StatusCode, successResponse);
+ }
+
+ [HttpPost("{productId}")]
+ public async Task AddReview(Guid productId, CreateReviewDto review)
+ {
+ var result = await mediator.Send(new AddReview(productId, review));
+ if (result is ErrorResponse errorResponse)
+ return StatusCode(errorResponse.StatusCode, errorResponse);
+
+ var successResponse = (SuccessResponse)result;
+ return StatusCode(successResponse.StatusCode, successResponse);
+ }
+
+ [HttpGet("{id}")]
+ public async Task GetProductReview(Guid id, Guid? clientId, int page, int pageSize)
+ {
+ var result = await mediator
+ .Send(new GetProductReviewById(id, clientId, new PaginationRequest(page, pageSize)));
+ if (result is ErrorResponse errorResponse)
+ return StatusCode(errorResponse.StatusCode, errorResponse);
+ var successResponse = (SuccessResponse)result;
+ return StatusCode(successResponse.StatusCode, successResponse);
+ }
+
+ [HttpPatch("{productId}")]
+ public async Task UpdateReview(Guid productId, Guid clientId, UpdateReviewDto updateReviewDto)
+ {
+ var result = await mediator.Send(new UpdateReview(productId, clientId, updateReviewDto));
+ if (result is ErrorResponse errorResponse)
+ return StatusCode(errorResponse.StatusCode, errorResponse);
+ var successResponse = (SuccessResponse)result;
+ return StatusCode(successResponse.StatusCode, successResponse);
+ }
+
+ [HttpDelete("{productId}")]
+ public async Task DeleteReview(Guid productId, Guid clientId)
+ {
+ var result = await mediator.Send(new DeleteReview(productId, clientId));
+ if (result is ErrorResponse errorResponse)
+ return StatusCode(errorResponse.StatusCode, errorResponse);
+ var successResponse = (SuccessResponse)result;
+ return StatusCode(successResponse.StatusCode, successResponse);
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/Program.cs b/apps/merchant-api/ReviewService/src/ReviewService.Api/Program.cs
new file mode 100644
index 00000000..671bdbce
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/Program.cs
@@ -0,0 +1,61 @@
+using Commons.ResponseHandler.Handler.Concretes;
+using Commons.ResponseHandler.Handler.Interfaces;
+using DotNetEnv;
+using FluentValidation;
+using MassTransit.Internals;
+using RabbitMQMessaging.Extensions;
+using ReviewService.Api;
+using ReviewService.Api.Configuration;
+using ReviewService.Application.Profiles;
+using ReviewService.Application.ValidationSettings;
+
+var builder = WebApplication.CreateBuilder(args);
+Env.Load("../../../.env");
+
+builder.Configuration.AddEnvironmentVariables();
+builder.Configuration.AddJsonFile("validationSettings.json", optional: false, reloadOnChange: true);
+builder.Services.Configure(builder.Configuration);
+
+var apiGateway = builder.Configuration["ApiGatewayUrl"] ?? "http://localhost:5001";
+builder.Services.AddCors(options =>
+{
+ options.AddPolicy("AllowedLocalhost",
+ policyBuilder => policyBuilder
+ .WithOrigins(apiGateway)
+ .AllowAnyHeader()
+ .AllowAnyMethod());
+});
+
+var connectionString = builder.Configuration["MONGODB_URI"];
+if (connectionString == null)
+{
+ Console.WriteLine("You must set your 'MONGODB_URI' environment variable. To learn how to set it, see https://www.mongodb.com/docs/drivers/csharp/current/quick-start/#set-your-connection-string");
+ Environment.Exit(0);
+}
+
+builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.ConfigureSwagger();
+
+builder.Services.AddRepositories();
+builder.Services.ConfigDbSet(connectionString);
+builder.Services.AddValidatorsFromAssembly(typeof(ReviewServiceProfile).Assembly);
+builder.Services.AddAutoMapper(typeof(ReviewServiceProfile));
+builder.Services.AddMediatR(cfg =>
+ cfg.RegisterServicesFromAssemblies(typeof(ReviewServiceProfile).Assembly));
+builder.Services.AddScoped();
+builder.Services.AddMassTransitWithRabbitMq("review");
+
+var app = builder.Build();
+
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.MapControllers();
+app.UseCors("AllowApiGateway");
+app.UseAuthentication();
+app.UseAuthorization();
+app.Run();
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/Properties/launchSettings.json b/apps/merchant-api/ReviewService/src/ReviewService.Api/Properties/launchSettings.json
new file mode 100644
index 00000000..7d779e24
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:12039",
+ "sslPort": 44349
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5500",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7269;http://localhost:5500",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/ReviewService.Api.csproj b/apps/merchant-api/ReviewService/src/ReviewService.Api/ReviewService.Api.csproj
new file mode 100644
index 00000000..ddfec229
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/ReviewService.Api.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+ ReviewService.Api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/SwaggerConf.cs b/apps/merchant-api/ReviewService/src/ReviewService.Api/SwaggerConf.cs
new file mode 100644
index 00000000..6df1dfeb
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/SwaggerConf.cs
@@ -0,0 +1,49 @@
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace ReviewService.Api;
+
+public static class SwaggerConf
+{
+ public static void ConfigureSwagger(this IServiceCollection services)
+ {
+ services.AddSwaggerGen(o =>
+ {
+ ConfigureApiKeyBearer(o);
+ AddApiKeyRequirement(o);
+ });
+ }
+
+ private static void ConfigureApiKeyBearer(SwaggerGenOptions o)
+ {
+ o.AddSecurityDefinition("ApiKeyBearer", new OpenApiSecurityScheme
+ {
+ In = ParameterLocation.Header,
+ Description = "Please enter the JWT token with the 'Bearer ' prefix",
+ Name = "Authorization",
+ Type = SecuritySchemeType.ApiKey,
+ Scheme = "Bearer"
+ });
+ }
+
+ private static void AddApiKeyRequirement(SwaggerGenOptions o)
+ {
+ o.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "ApiKeyBearer"
+ },
+ Scheme = "Bearer",
+ Name = "Authorization",
+ In = ParameterLocation.Header,
+ },
+ new List()
+ }
+ });
+ }
+}
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/appsettings.json b/apps/merchant-api/ReviewService/src/ReviewService.Api/appsettings.json
new file mode 100644
index 00000000..e0a4eaaa
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "ApiGatewayUrl": "http://localhost:5001",
+ "IdentityUrlVerification": ""
+}
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Api/validationSettings.json b/apps/merchant-api/ReviewService/src/ReviewService.Api/validationSettings.json
new file mode 100644
index 00000000..760386c9
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Api/validationSettings.json
@@ -0,0 +1,9 @@
+{
+ "Review": {
+ "MinLengthName": 3,
+ "MaxLengthName": 50,
+ "MaxLengthComment": 500,
+ "MinRatingRange": 0,
+ "MaxRatingRange": 5
+ }
+}
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/Commons/IPaginationRequest.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/Commons/IPaginationRequest.cs
new file mode 100644
index 00000000..2df09518
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/Commons/IPaginationRequest.cs
@@ -0,0 +1,7 @@
+namespace ReviewService.Application.CommandsQueries.Commons;
+
+public class PaginationRequest(int page, int pageSize)
+{
+ public int Page { get; set; } = page;
+ public int PageSize { get; set; } = pageSize;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/AddReviewHandler.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/AddReviewHandler.cs
new file mode 100644
index 00000000..91739f57
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/AddReviewHandler.cs
@@ -0,0 +1,67 @@
+using AutoMapper;
+using Commons.Messages;
+using Commons.ResponseHandler.Handler.Interfaces;
+using Commons.ResponseHandler.Responses.Bases;
+using FluentValidation;
+using MassTransit;
+using MediatR;
+using MongoDB.Driver;
+using ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+using ReviewService.Application.Dtos.Review;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Handlers;
+
+public class AddReviewHandler(
+ IRepository repository,
+ IValidator validator,
+ IResponseHandlingHelper responseHandlingHelper,
+ IMapper mapper,
+ IBus producer)
+ : IRequestHandler
+{
+ public async Task Handle(AddReview request, CancellationToken cancellationToken)
+ {
+ var result = await validator.ValidateAsync(request.Review, cancellationToken);
+ if (!result.IsValid)
+ return responseHandlingHelper.BadRequest(
+ "Validations failed",
+ result.Errors.Select(e => e.ErrorMessage).ToList());
+ var productReview = await repository.GetByIdAsync(request.ProductId);
+
+ if (productReview == null)
+ {
+ await repository.AddAsync(new Concretes.ProductReview
+ {
+ ProductId = request.ProductId,
+ Reviews = [mapper.Map(request.Review)]
+ });
+ return responseHandlingHelper.Ok("Review added successfully", mapper.Map(request.Review));
+ }
+
+ var clientReview = productReview.Reviews.Find(r => r.ClientId == request.Review.ClientId);
+ if (clientReview is { IsActive: true })
+ return responseHandlingHelper.BadRequest("Client already make review for this product");
+
+ var reviewsUpdated = productReview.Reviews;
+ reviewsUpdated.Add(mapper.Map(request.Review));
+ var updateBuilder = new UpdateDefinitionBuilder()
+ .Set>(e => e.Reviews, reviewsUpdated);
+
+ var response = await repository.UpdateAsync(productReview.Id, productReview, updateBuilder);
+
+ float ratingSummation = reviewsUpdated.Aggregate(0, (i, review) => i + review.Rating);
+ var averageRating = ratingSummation / reviewsUpdated.Count;
+
+ await producer.Publish(new RatingMessage
+ {
+ ProductId = request.ProductId,
+ Rating = averageRating
+ });
+
+ if (response < 0)
+ return responseHandlingHelper.InternalServerError("Some error happens while try to add review");
+ return responseHandlingHelper.Ok("Review added successfully", mapper.Map(request.Review));
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/CreateProductReviewHandler.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/CreateProductReviewHandler.cs
new file mode 100644
index 00000000..0fc37dbe
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/CreateProductReviewHandler.cs
@@ -0,0 +1,32 @@
+using AutoMapper;
+using Commons.ResponseHandler.Handler.Concretes;
+using Commons.ResponseHandler.Handler.Interfaces;
+using Commons.ResponseHandler.Responses.Bases;
+using FluentValidation;
+using MediatR;
+using ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+using ReviewService.Application.Dtos.ProductReview;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Handlers;
+
+public class CreateProductReviewHandler(
+ IRepository repository,
+ IValidator validator,
+ IResponseHandlingHelper responseHandlingHelper,
+ IMapper mapper)
+ : IRequestHandler
+{
+ public async Task Handle(CreateProductReview request, CancellationToken cancellationToken)
+ {
+ var result = await validator.ValidateAsync(request.ProductReview, cancellationToken);
+ if (!result.IsValid) return responseHandlingHelper
+ .BadRequest(
+ "Validations failed",
+ result.Errors.Select(e => e.ErrorMessage).ToList());
+
+ var productReview = mapper.Map(request.ProductReview);
+ await repository.AddAsync(productReview);
+ return responseHandlingHelper.Created("Product review successfully added", productReview);
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/DeleteReviewHandler.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/DeleteReviewHandler.cs
new file mode 100644
index 00000000..b0d9cf7d
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/DeleteReviewHandler.cs
@@ -0,0 +1,38 @@
+using Commons.ResponseHandler.Handler.Interfaces;
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+using MongoDB.Driver;
+using ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Handlers;
+
+public class DeleteReviewHandler(
+ IRepository repository,
+ IResponseHandlingHelper responseHandlingHelper)
+ : IRequestHandler
+{
+ public async Task Handle(DeleteReview request, CancellationToken cancellationToken)
+ {
+ var productReview = await repository.GetByIdAsync(request.ProductId);
+ if (productReview == null)
+ return responseHandlingHelper.NotFound("Product review not found");
+ var review = productReview.Reviews.Find(r => r.ClientId == request.ClientId);
+ if (review == null)
+ return responseHandlingHelper.NotFound("Client's review not found");
+
+ var reviewsUpdated = productReview.Reviews.Where(r => r.ClientId != request.ClientId).ToList();
+ review.IsActive = false;
+ review.DeletedAt = DateTime.UtcNow;
+ reviewsUpdated.Add(review);
+
+ var updateBuilder = new UpdateDefinitionBuilder()
+ .Set>(e => e.Reviews, reviewsUpdated);
+ var response = await repository.UpdateAsync(request.ProductId, productReview, updateBuilder);
+
+ return response > 0
+ ? responseHandlingHelper.Ok("Review deleted successfully", review)
+ : responseHandlingHelper.InternalServerError("Some error happens while try to delete review");
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/UpdateReviewHandler.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/UpdateReviewHandler.cs
new file mode 100644
index 00000000..0874d519
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Handlers/UpdateReviewHandler.cs
@@ -0,0 +1,38 @@
+using Commons.ResponseHandler.Handler.Interfaces;
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+using MongoDB.Driver;
+using ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Handlers;
+
+public class UpdateReviewHandler(
+ IRepository repository,
+ IResponseHandlingHelper responseHandlingHelper)
+ : IRequestHandler
+{
+ public async Task Handle(UpdateReview request, CancellationToken cancellationToken)
+ {
+ var productReview = await repository.GetByIdAsync(request.ProductId);
+ if (productReview == null)
+ return responseHandlingHelper.NotFound("Product review not found");
+ var review = productReview.Reviews.Find(r => r.ClientId == request.ClientId && r.IsActive);
+ if (review == null) return responseHandlingHelper.NotFound("Client's review not found");
+
+ var reviewsExcluded = productReview.Reviews.Where(r => r.Id != review.Id);
+ review.Comment = request.UpdateReviewDto.Comment ?? review.Comment;
+ review.Rating = request.UpdateReviewDto.Rating ?? review.Rating;
+ review.UpdatedAt = DateTime.UtcNow;
+ var reviewsUpdated = reviewsExcluded.Append(review);
+
+ var updateBuilder = new UpdateDefinitionBuilder()
+ .Set>(e => e.Reviews, reviewsUpdated.ToList());
+ var response = await repository.UpdateAsync(productReview.Id, productReview, updateBuilder);
+
+ return response > 0
+ ? responseHandlingHelper.Ok("Review updated successfully", review)
+ : responseHandlingHelper.InternalServerError("Some error happens while review updating");
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/AddReview.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/AddReview.cs
new file mode 100644
index 00000000..6b3b7b9e
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/AddReview.cs
@@ -0,0 +1,11 @@
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+using ReviewService.Application.Dtos.Review;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+
+public class AddReview(Guid productId, CreateReviewDto review) : IRequest
+{
+ public Guid ProductId { get; set; } = productId;
+ public CreateReviewDto Review { get; set; } = review;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/CreateProductReview.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/CreateProductReview.cs
new file mode 100644
index 00000000..e106425b
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/CreateProductReview.cs
@@ -0,0 +1,10 @@
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+using ReviewService.Application.Dtos.ProductReview;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+
+public class CreateProductReview(CreateProductReviewDto productReview) : IRequest
+{
+ public CreateProductReviewDto ProductReview { get; set; } = productReview;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/DeleteReview.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/DeleteReview.cs
new file mode 100644
index 00000000..f36b6d69
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/DeleteReview.cs
@@ -0,0 +1,10 @@
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+
+public class DeleteReview(Guid productId, Guid clientId) : IRequest
+{
+ public Guid ProductId { get; set; } = productId;
+ public Guid ClientId { get; set; } = clientId;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/UpdateReview.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/UpdateReview.cs
new file mode 100644
index 00000000..245fa184
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Commands/Requests/UpdateReview.cs
@@ -0,0 +1,12 @@
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+using ReviewService.Application.Dtos.Review;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+
+public class UpdateReview(Guid productId, Guid clientId, UpdateReviewDto updateReviewDto) : IRequest
+{
+ public Guid ProductId { get; set; } = productId;
+ public Guid ClientId { get; set; } = clientId;
+ public UpdateReviewDto UpdateReviewDto { get; set; } = updateReviewDto;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Queries/Handlers/GetProductReviewByIdHandler.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Queries/Handlers/GetProductReviewByIdHandler.cs
new file mode 100644
index 00000000..1fde7589
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Queries/Handlers/GetProductReviewByIdHandler.cs
@@ -0,0 +1,54 @@
+using Commons.Dto;
+using Commons.ResponseHandler.Handler.Interfaces;
+using Commons.ResponseHandler.Responses.Bases;
+using FluentValidation;
+using MediatR;
+using ReviewService.Application.CommandsQueries.Commons;
+using ReviewService.Application.CommandsQueries.ProductReview.Queries.Requests;
+using ReviewService.Application.Dtos.ProductReview;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Queries.Handlers;
+
+public class GetProductReviewByIdHandler(
+ IRepository repository,
+ IValidator validator,
+ IResponseHandlingHelper responseHandlingHelper)
+ : IRequestHandler
+{
+ public async Task Handle(GetProductReviewById request, CancellationToken cancellationToken)
+ {
+ var result = await validator.ValidateAsync(request.PaginationRequest, cancellationToken);
+ if (!result.IsValid)
+ return responseHandlingHelper.BadRequest("Bad pagination request");
+
+ var response = await repository.GetByIdAsync(request.ProductId);
+ if (response == null)
+ return responseHandlingHelper.NotFound("Product review not found");
+ double summationRating = 0;
+ response.Reviews.ForEach(r => summationRating += r.IsActive ? r.Rating : 0);
+
+ var paginatedReviews = response.Reviews
+ .Where(r => r.IsActive)
+ .OrderByDescending(r => r.Rating)
+ .ThenBy(r => r.CreatedAt)
+ .Skip((request.PaginationRequest.Page - 1) * request.PaginationRequest.PageSize)
+ .Take(request.PaginationRequest.PageSize).ToList();
+ var paginatedResponse = new PaginatedResponseDto(
+ paginatedReviews,
+ response.Reviews.Count(r => r.IsActive),
+ request.PaginationRequest.Page,
+ request.PaginationRequest.PageSize);
+
+ var clients = response.Reviews.Where(r => r.IsActive).Select(r => r.ClientId);
+ var ableToAdd = request.ClientId != null && !clients.Contains((Guid)request.ClientId);
+
+ return responseHandlingHelper.Ok("Product review found", new ProductReviewDto
+ {
+ AverageRating = Math.Round((summationRating / response.Reviews.Count(r => r.IsActive)), 1),
+ Reviews = paginatedResponse,
+ ClientAbleToAdd = ableToAdd
+ });
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Queries/Requests/GetProductReviewById.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Queries/Requests/GetProductReviewById.cs
new file mode 100644
index 00000000..4c5414b7
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/CommandsQueries/ProductReview/Queries/Requests/GetProductReviewById.cs
@@ -0,0 +1,12 @@
+using Commons.ResponseHandler.Responses.Bases;
+using MediatR;
+using ReviewService.Application.CommandsQueries.Commons;
+
+namespace ReviewService.Application.CommandsQueries.ProductReview.Queries.Requests;
+
+public class GetProductReviewById(Guid productId, Guid? clientId, PaginationRequest paginationRequest) : IRequest
+{
+ public Guid ProductId { get; set; } = productId;
+ public Guid? ClientId { get; set; } = clientId;
+ public PaginationRequest PaginationRequest { get; set; } = paginationRequest;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/ProductReview/CreateProductReviewDto.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/ProductReview/CreateProductReviewDto.cs
new file mode 100644
index 00000000..482e1652
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/ProductReview/CreateProductReviewDto.cs
@@ -0,0 +1,9 @@
+using ReviewService.Application.Dtos.Review;
+
+namespace ReviewService.Application.Dtos.ProductReview;
+
+public class CreateProductReviewDto
+{
+ public required Guid ProductId { get; set; }
+ public required List Reviews { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/ProductReview/ProductReviewDto.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/ProductReview/ProductReviewDto.cs
new file mode 100644
index 00000000..08e1c970
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/ProductReview/ProductReviewDto.cs
@@ -0,0 +1,12 @@
+using Commons.Dto;
+using ReviewService.Application.CommandsQueries.Commons;
+using ReviewService.Concretes;
+
+namespace ReviewService.Application.Dtos.ProductReview;
+
+public class ProductReviewDto
+{
+ public required double AverageRating { get; set; }
+ public required bool ClientAbleToAdd { get; set; } = true;
+ public required PaginatedResponseDto Reviews { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/Review/CreateReviewDto.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/Review/CreateReviewDto.cs
new file mode 100644
index 00000000..b4e908d6
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/Review/CreateReviewDto.cs
@@ -0,0 +1,9 @@
+namespace ReviewService.Application.Dtos.Review;
+
+public class CreateReviewDto
+{
+ public required Guid ClientId { get; set; }
+ public required string ClientName { get; set; }
+ public required uint Rating { get; set; }
+ public string? Comment { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/Review/UpdateReviewDto.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/Review/UpdateReviewDto.cs
new file mode 100644
index 00000000..921f2b37
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Dtos/Review/UpdateReviewDto.cs
@@ -0,0 +1,7 @@
+namespace ReviewService.Application.Dtos.Review;
+
+public class UpdateReviewDto
+{
+ public int? Rating { get; set; }
+ public string? Comment { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Profiles/ReviewServiceProfile.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Profiles/ReviewServiceProfile.cs
new file mode 100644
index 00000000..f83ef978
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Profiles/ReviewServiceProfile.cs
@@ -0,0 +1,17 @@
+using AutoMapper;
+using ReviewService.Application.CommandsQueries.ProductReview.Commands.Requests;
+using ReviewService.Application.Dtos.ProductReview;
+using ReviewService.Application.Dtos.Review;
+using ReviewService.Concretes;
+
+namespace ReviewService.Application.Profiles;
+
+public class ReviewServiceProfile : Profile
+{
+ public ReviewServiceProfile()
+ {
+ CreateMap().ReverseMap();
+ CreateMap().ReverseMap();
+ CreateMap().ReverseMap();
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Program.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Program.cs
new file mode 100644
index 00000000..e5dff12b
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Program.cs
@@ -0,0 +1,3 @@
+// See https://aka.ms/new-console-template for more information
+
+Console.WriteLine("Hello, World!");
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/ReviewService.Application.csproj b/apps/merchant-api/ReviewService/src/ReviewService.Application/ReviewService.Application.csproj
new file mode 100644
index 00000000..59570056
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/ReviewService.Application.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ProductReviewSettings.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ProductReviewSettings.cs
new file mode 100644
index 00000000..816fa938
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ProductReviewSettings.cs
@@ -0,0 +1,6 @@
+namespace ReviewService.Application.ValidationSettings;
+
+public class ProductReviewSettings
+{
+
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ReviewSettings.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ReviewSettings.cs
new file mode 100644
index 00000000..802548b4
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ReviewSettings.cs
@@ -0,0 +1,11 @@
+namespace ReviewService.Application.ValidationSettings;
+
+public class ReviewSettings
+{
+ public int MinLengthName { get; set; }
+ public int MaxLengthName { get; set; }
+
+ public int MaxLengthComment { get; set; }
+ public int MinRatingRange { get; set; }
+ public int MaxRatingRange { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ValidationSettings.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ValidationSettings.cs
new file mode 100644
index 00000000..570302be
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/ValidationSettings/ValidationSettings.cs
@@ -0,0 +1,6 @@
+namespace ReviewService.Application.ValidationSettings;
+
+public class ValidationSettings
+{
+ public ReviewSettings Review { get; set; } = new();
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Commons/PaginationRequestValidator.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Commons/PaginationRequestValidator.cs
new file mode 100644
index 00000000..599a36cd
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Commons/PaginationRequestValidator.cs
@@ -0,0 +1,17 @@
+using FluentValidation;
+using ReviewService.Application.CommandsQueries.Commons;
+
+namespace ReviewService.Application.Validators.Commons;
+
+public class PaginationRequestValidator : AbstractValidator
+{
+ public PaginationRequestValidator()
+ {
+ RuleFor(e => e.Page)
+ .NotNull()
+ .Must(p => p >= 0).WithMessage("Page should be greater than zero");
+ RuleFor(e => e.PageSize)
+ .NotNull()
+ .Must(p => p >= 0).WithMessage("Page should be greater than zero");
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/ProductReview/CreateProductReviewDtoValidator.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/ProductReview/CreateProductReviewDtoValidator.cs
new file mode 100644
index 00000000..d2dd6579
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/ProductReview/CreateProductReviewDtoValidator.cs
@@ -0,0 +1,16 @@
+using FluentValidation;
+using Microsoft.Extensions.Options;
+using ReviewService.Application.Dtos.ProductReview;
+using ReviewService.Application.Validators.Review;
+
+namespace ReviewService.Application.Validators.ProductReview;
+
+public class CreateProductReviewDtoValidator : AbstractValidator
+{
+ public CreateProductReviewDtoValidator(IOptions validationSettings)
+ {
+ RuleFor(e => e.ProductId).NotNull();
+ RuleForEach(e => e.Reviews)
+ .SetValidator(new CreateReviewDtoValidator(validationSettings));
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Review/CreateReviewDtoValidator.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Review/CreateReviewDtoValidator.cs
new file mode 100644
index 00000000..12360fbb
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Review/CreateReviewDtoValidator.cs
@@ -0,0 +1,24 @@
+using FluentValidation;
+using Microsoft.Extensions.Options;
+using ReviewService.Application.Dtos.Review;
+
+namespace ReviewService.Application.Validators.Review;
+
+public class CreateReviewDtoValidator : AbstractValidator
+{
+ public CreateReviewDtoValidator(IOptions validationSettings)
+ {
+ var reviewSettings = validationSettings.Value.Review;
+ RuleFor(r => r.ClientId).NotNull();
+ RuleFor(r => r.ClientName)
+ .NotNull()
+ .MinimumLength(reviewSettings.MinLengthName)
+ .MaximumLength(reviewSettings.MaxLengthName);
+ RuleFor(r => r.Comment)
+ .MaximumLength(reviewSettings.MaxLengthComment);
+ RuleFor(r => r.Rating)
+ .NotNull()
+ .Must(r => r <= reviewSettings.MaxRatingRange).WithMessage($"The rating should be smaller than {reviewSettings.MaxRatingRange}")
+ .Must(r => r >= reviewSettings.MinRatingRange).WithMessage($"The rating should be bigger than {reviewSettings.MinRatingRange}");
+ }
+}
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Review/ReviewValidator.cs b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Review/ReviewValidator.cs
new file mode 100644
index 00000000..59ae0c2a
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Application/Validators/Review/ReviewValidator.cs
@@ -0,0 +1,24 @@
+using FluentValidation;
+using Microsoft.Extensions.Options;
+
+namespace ReviewService.Application.Validators.Review;
+
+public class ReviewValidator : AbstractValidator
+{
+ public ReviewValidator(IOptions validationSettings)
+ {
+ var reviewSettings = validationSettings.Value.Review;
+ RuleFor(r => r.ClientId).NotNull();
+ RuleFor(r => r.ClientName)
+ .NotNull()
+ .MinimumLength(reviewSettings.MinLengthName)
+ .MaximumLength(reviewSettings.MaxLengthName);
+ RuleFor(r => r.Comment)
+ .NotEmpty()
+ .MaximumLength(reviewSettings.MaxLengthComment);
+ RuleFor(r => r.Rating)
+ .NotNull()
+ .Must(r => r <= reviewSettings.MaxRatingRange).WithMessage($"The rating should be smaller than {reviewSettings.MaxRatingRange}")
+ .Must(r => r >= reviewSettings.MinRatingRange).WithMessage($"The rating should be bigger than {reviewSettings.MinRatingRange}");
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Bases/BaseEntity.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Bases/BaseEntity.cs
new file mode 100644
index 00000000..2e1ccc4e
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Bases/BaseEntity.cs
@@ -0,0 +1,15 @@
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using ReviewService.Interfaces;
+
+namespace ReviewService.Bases;
+
+public class BaseEntity : BaseRegister, IEntity
+{
+ [BsonId]
+ [BsonGuidRepresentation(GuidRepresentation.Standard)]
+ public Guid Id { get; set; } = Guid.NewGuid();
+
+ [BsonElement("is_active")]
+ public bool IsActive { get; set; } = true;
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Bases/BaseRegister.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Bases/BaseRegister.cs
new file mode 100644
index 00000000..b671ba3d
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Bases/BaseRegister.cs
@@ -0,0 +1,16 @@
+using MongoDB.Bson.Serialization.Attributes;
+using ReviewService.Interfaces;
+
+namespace ReviewService.Bases;
+
+public class BaseRegister : IRegister
+{
+ [BsonElement("created_at")]
+ public DateTime CreatedAt { get; } = DateTime.Now;
+
+ [BsonElement("updated_at")]
+ public DateTime? UpdatedAt { get; set; }
+
+ [BsonElement("deleted_at")]
+ public DateTime? DeletedAt { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Class1.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Class1.cs
new file mode 100644
index 00000000..e3238f8d
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Class1.cs
@@ -0,0 +1,5 @@
+namespace ReviewService;
+
+public class Class1
+{
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Concretes/ProductReview.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Concretes/ProductReview.cs
new file mode 100644
index 00000000..189b05fe
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Concretes/ProductReview.cs
@@ -0,0 +1,25 @@
+using System.Text;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using ReviewService.Bases;
+
+namespace ReviewService.Concretes;
+
+public class ProductReview : BaseEntity
+{
+ [BsonElement("product_id")]
+ [BsonGuidRepresentation(GuidRepresentation.Standard)]
+ public required Guid ProductId { get; set; }
+
+ [BsonElement("reviews")]
+ public required List Reviews { get; set; } = new();
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder("ProductReview: {");
+ sb.Append($"ID: {Id}, ");
+ sb.Append($"ProductId: {ProductId}, ");
+ sb.Append($"Reviews: {Reviews.ToString()}");
+ return sb.ToString();
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Concretes/Review.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Concretes/Review.cs
new file mode 100644
index 00000000..99d06a8a
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Concretes/Review.cs
@@ -0,0 +1,35 @@
+using System.Text;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using ReviewService.Bases;
+
+namespace ReviewService.Concretes;
+
+public class Review : BaseEntity
+{
+
+ [BsonElement("client_id")]
+ [BsonGuidRepresentation(GuidRepresentation.Standard)]
+ public required Guid ClientId { get; set; }
+
+ [BsonElement("client_name")]
+ public required string ClientName { get; set; }
+
+ [BsonElement("rating")]
+ public required int Rating { get; set; }
+
+ [BsonElement("comment")]
+ public string? Comment { get; set; }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder("{Review: {");
+ sb.Append($"ClientID: {ClientName}, ");
+ sb.Append($"ClientName: {ClientName}, ");
+ sb.Append($"Rating: {Rating}, ");
+ sb.Append($"Comment: {Comment}");
+ sb.Append("}}");
+
+ return sb.ToString();
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Interfaces/IEntity.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Interfaces/IEntity.cs
new file mode 100644
index 00000000..e8028fb3
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Interfaces/IEntity.cs
@@ -0,0 +1,7 @@
+namespace ReviewService.Interfaces;
+
+public interface IEntity : IRegister
+{
+ Guid Id { get; }
+ bool IsActive { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/Interfaces/IRegister.cs b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Interfaces/IRegister.cs
new file mode 100644
index 00000000..ddbe4ee7
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/Interfaces/IRegister.cs
@@ -0,0 +1,8 @@
+namespace ReviewService.Interfaces;
+
+public interface IRegister
+{
+ DateTime CreatedAt { get; }
+ DateTime? UpdatedAt { get; set; }
+ DateTime? DeletedAt { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Domain/ReviewService.Domain.csproj b/apps/merchant-api/ReviewService/src/ReviewService.Domain/ReviewService.Domain.csproj
new file mode 100644
index 00000000..94749b2a
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Domain/ReviewService.Domain.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+ ReviewService
+
+
+
+
+
+
+
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Context/Concretes/ProductReviewContext.cs b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Context/Concretes/ProductReviewContext.cs
new file mode 100644
index 00000000..f9444e26
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Context/Concretes/ProductReviewContext.cs
@@ -0,0 +1,11 @@
+using MongoDB.Driver;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Context.Interfaces;
+
+namespace ReviewService.Infrastructure.Context.Concretes;
+
+public class ProductReviewContext(IMongoDatabase database) : IContext
+{
+ public IMongoCollection Collection { get; set; }
+ = database.GetCollection("product_review");
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Context/Interfaces/IContext.cs b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Context/Interfaces/IContext.cs
new file mode 100644
index 00000000..84482335
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Context/Interfaces/IContext.cs
@@ -0,0 +1,9 @@
+using MongoDB.Driver;
+using ReviewService.Interfaces;
+
+namespace ReviewService.Infrastructure.Context.Interfaces;
+
+public interface IContext where T : IEntity
+{
+ IMongoCollection Collection { get; set; }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Program.cs b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Program.cs
new file mode 100644
index 00000000..e5dff12b
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Program.cs
@@ -0,0 +1,3 @@
+// See https://aka.ms/new-console-template for more information
+
+Console.WriteLine("Hello, World!");
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Bases/BaseRepository.cs b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Bases/BaseRepository.cs
new file mode 100644
index 00000000..4e7fbb75
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Bases/BaseRepository.cs
@@ -0,0 +1,72 @@
+using MongoDB.Driver;
+using ReviewService.Infrastructure.Context.Interfaces;
+using ReviewService.Infrastructure.Repositories.Interfaces;
+using ReviewService.Interfaces;
+
+namespace ReviewService.Infrastructure.Repositories.Bases;
+
+public class BaseRepository(IContext context) : IRepository where T : IEntity
+{
+ public virtual async Task GetByIdAsync(Guid id)
+ {
+ var filter = Builders.Filter
+ .Eq(entity => entity.Id, id);
+ var entity = await context.Collection.Find(filter).FirstOrDefaultAsync();
+ return entity;
+ }
+
+ public async Task> GetAllAsync(int pageNumber, int pageSize)
+ {
+ var filter = Builders.Filter
+ .Eq(entity => entity.IsActive, true);
+ var collection = await context.Collection.Find(filter)
+ .SortBy(e => e.CreatedAt)
+ .Skip((pageNumber - 1) * pageSize)
+ .Limit(pageSize).ToListAsync();
+ return collection;
+ }
+
+ public async Task CountAsync()
+ {
+ var filter = Builders.Filter
+ .Eq(entity => entity.IsActive, true);
+ var quantity = await context.Collection.Find(filter).CountDocumentsAsync();
+ return (uint)quantity;
+ }
+
+ public async Task AddAsync(T entity)
+ {
+ try
+ {
+ await context.Collection.InsertOneAsync(entity);
+ return entity;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+
+ public async Task UpdateAsync(Guid id, T entity, UpdateDefinition update)
+ {
+ entity.UpdatedAt = DateTime.UtcNow;
+
+ var filter = Builders.Filter
+ .Eq(e => e.Id, id);
+
+ var updateDefinition = update.Set(e => e.UpdatedAt, DateTime.UtcNow);
+
+ var response = await context.Collection.UpdateOneAsync(filter, updateDefinition);
+ return (int)response.ModifiedCount;
+ }
+
+ public async Task DeleteAsync(Guid id)
+ {
+ var filter = Builders.Filter
+ .Eq(e => e.Id, id);
+
+ var result = await context.Collection.DeleteOneAsync(filter);
+ return (int)result.DeletedCount;
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Concretes/ProductReviewRepository.cs b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Concretes/ProductReviewRepository.cs
new file mode 100644
index 00000000..c09d3f39
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Concretes/ProductReviewRepository.cs
@@ -0,0 +1,19 @@
+using MongoDB.Driver;
+using ReviewService.Concretes;
+using ReviewService.Infrastructure.Context.Interfaces;
+using ReviewService.Infrastructure.Repositories.Bases;
+
+namespace ReviewService.Infrastructure.Repositories.Concretes;
+
+public class ProductReviewRepository(IContext context) : BaseRepository(context)
+{
+ private readonly IContext _context = context;
+
+ public override async Task GetByIdAsync(Guid id)
+ {
+ var filter = Builders.Filter
+ .Eq(entity => entity.ProductId, id);
+ var entity = await _context.Collection.Find(filter).FirstOrDefaultAsync();
+ return entity;
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Interfaces/IRepository.cs b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Interfaces/IRepository.cs
new file mode 100644
index 00000000..d4e2658e
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/Repositories/Interfaces/IRepository.cs
@@ -0,0 +1,14 @@
+using MongoDB.Driver;
+using ReviewService.Interfaces;
+
+namespace ReviewService.Infrastructure.Repositories.Interfaces;
+
+public interface IRepository where T : IEntity
+{
+ Task GetByIdAsync(Guid id);
+ Task> GetAllAsync(int pageNumber, int pageSize);
+ Task CountAsync();
+ Task AddAsync(T entity);
+ Task UpdateAsync(Guid id, T entity, UpdateDefinition update);
+ Task DeleteAsync(Guid id);
+}
\ No newline at end of file
diff --git a/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/ReviewService.Infrastructure.csproj b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/ReviewService.Infrastructure.csproj
new file mode 100644
index 00000000..5e45a840
--- /dev/null
+++ b/apps/merchant-api/ReviewService/src/ReviewService.Infrastructure/ReviewService.Infrastructure.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/merchant-api/ReviewService/test/TestProject1/TestProject1.csproj b/apps/merchant-api/ReviewService/test/TestProject1/TestProject1.csproj
new file mode 100644
index 00000000..39c01e67
--- /dev/null
+++ b/apps/merchant-api/ReviewService/test/TestProject1/TestProject1.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/merchant-api/ReviewService/test/TestProject1/UnitTest1.cs b/apps/merchant-api/ReviewService/test/TestProject1/UnitTest1.cs
new file mode 100644
index 00000000..77d2dc6f
--- /dev/null
+++ b/apps/merchant-api/ReviewService/test/TestProject1/UnitTest1.cs
@@ -0,0 +1,15 @@
+namespace TestProject1;
+
+public class Tests
+{
+ [SetUp]
+ public void Setup()
+ {
+ }
+
+ [Test]
+ public void Test1()
+ {
+ Assert.Pass();
+ }
+}
\ No newline at end of file
diff --git a/apps/merchant-api/docker-compose.yml b/apps/merchant-api/docker-compose.yml
index f34f6c89..aabe9001 100644
--- a/apps/merchant-api/docker-compose.yml
+++ b/apps/merchant-api/docker-compose.yml
@@ -148,6 +148,43 @@ services:
depends_on:
rabbitmq:
condition: service_healthy
+
+ reviews_db:
+ image: mongo:7.0.16-rc1
+ restart: always
+ ports:
+ - "${REVIEW_DB_PORT}:${REVIEW_DB_PORT}"
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: ${REVIEW_DB_USER}
+ MONGO_INITDB_ROOT_PASSWORD: ${REVIEW_DB_PASS}
+ volumes:
+ - mongodb_data:/data/db
+ networks:
+ - merchant-network
+
+ review_service:
+ build:
+ context: .
+ dockerfile: ReviewService/Dockerfile
+ ports:
+ - "5500:8080"
+ environment:
+ - ASPNETCORE_ENVIRONMENT=Development
+ - REVIEW_DB_PORT=${REVIEW_DB_PORT}
+ - REVIEW_DB_USER=${REVIEW_DB_USER}
+ - REVIEW_DB_PASS=${REVIEW_DB_PASS}
+ - MONGODB_URI=${MONGODB_URI}
+ - RABBITMQ_CONNECTION_STRING=${RABBITMQ_CONNECTION_STRING}
+ - RABBITMQ_USER=${RABBITMQ_USER}
+ - RABBITMQ_PASS=${RABBITMQ_PASS}
+ - RABBITMQ_HOST=${RABBITMQ_HOST}
+ - RABBITMQ_PORT=${RABBITMQ_PORT}
+ - RABBITMQ_VHOST=${RABBITMQ_VHOST}
+ networks:
+ - merchant-network
+ depends_on:
+ rabbitmq:
+ condition: service_healthy
networks:
merchant-network:
@@ -156,3 +193,4 @@ networks:
volumes:
postgres_data:
rabbitmq_data:
+ mongodb_data:
\ No newline at end of file
diff --git a/apps/merchant-api/merchant-api.sln b/apps/merchant-api/merchant-api.sln
index 2d0708e0..defdce0a 100644
--- a/apps/merchant-api/merchant-api.sln
+++ b/apps/merchant-api/merchant-api.sln
@@ -3,77 +3,91 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "ApiGateway\ApiGateway.csproj", "{72FEA5ED-F129-4180-AEB1-B635528E5773}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiGateway", "ApiGateway\ApiGateway.csproj", "{1071D398-B0D1-4C77-AC18-E8E2792A7B38}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MerchantCommon", "MerchantCommon", "{E65BEF82-5698-437E-847B-FFA6BD340DB2}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MerchantCommon", "MerchantCommon", "{8D623CEF-6CAA-4220-A85D-C1768B1602B4}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMqMessaging", "MerchantCommon\RabbitMqMessaging\RabbitMqMessaging.csproj", "{C7D3696D-4B48-465E-AEE9-5124ACED816F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMqMessaging", "MerchantCommon\RabbitMqMessaging\RabbitMqMessaging.csproj", "{2E740634-8267-4EA5-9C18-9C89487E7A0F}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commons", "MerchantCommon\Commons\Commons.csproj", "{641D5831-29F7-4E30-ADA3-2B278479F4C3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commons", "MerchantCommon\Commons\Commons.csproj", "{86D9DEF0-38DC-4559-8E1F-93687B4B9291}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PaymentService", "PaymentService", "{D4DE45EE-E027-43ED-BB50-BF5136664DD4}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PaymentService", "PaymentService", "{A91C63E4-FAE1-4D99-9BD0-880A5202BAD3}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EC25300E-BE2E-4987-B7A1-AC8AA9380F2D}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D8A3298A-2E52-4DE1-84B2-97D2ED1A3680}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Domain", "PaymentService\src\PaymentService.Domain\PaymentService.Domain.csproj", "{3696BCB5-5C0A-4060-B079-579A9D8D2A6E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Domain", "PaymentService\src\PaymentService.Domain\PaymentService.Domain.csproj", "{9A6CD5DF-72C7-4997-894A-EE2294EE1917}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Application", "PaymentService\src\PaymentService.Application\PaymentService.Application.csproj", "{AA82A51C-5725-44D4-9265-6A05F8AC4DD2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Application", "PaymentService\src\PaymentService.Application\PaymentService.Application.csproj", "{DF0F7F3F-08A8-4393-8846-900BC1D518B0}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Infrastructure", "PaymentService\src\PaymentService.Infrastructure\PaymentService.Infrastructure.csproj", "{1DFAC371-9AF7-427B-9483-2A5140EBB620}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Infrastructure", "PaymentService\src\PaymentService.Infrastructure\PaymentService.Infrastructure.csproj", "{3F75D64C-9B03-4FC1-A46E-11F6A5881EE7}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Commons", "PaymentService\src\PaymentService.Commons\PaymentService.Commons.csproj", "{B08D111D-B003-419C-AE8A-145FE1571105}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Api", "PaymentService\src\PaymentService.Api\PaymentService.Api.csproj", "{F8A61614-D1C9-4EB2-979B-A9AABDBBCD58}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaymentService.Api", "PaymentService\src\PaymentService.Api\PaymentService.Api.csproj", "{46F79B66-C7ED-4F03-9169-BB7FC5F9F552}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InventoryService", "InventoryService", "{50C4F989-1DC5-4085-B0DA-6ABE54D57CE3}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InventoryService", "InventoryService", "{B72BE68D-0106-46A6-A281-D912E90703C3}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{4B66FB5F-B6AC-4097-BFFA-C613967F34EC}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{57E1E4D9-98FE-424E-AC8D-47C50537CDC8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryServiceTests", "InventoryService\test\InventoryServiceTests\InventoryServiceTests.csproj", "{35374A82-8786-4817-A4F1-1CF317C16597}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryServiceTests", "InventoryService\test\InventoryServiceTests\InventoryServiceTests.csproj", "{2D0FCE69-11D5-410C-9879-B3969C4BCF9A}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{613127B5-4792-4540-A58F-A415ED4A4BC9}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{00EE6191-1D03-460D-A6B8-41C5A13C8CBE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Intraestructure", "InventoryService\src\InventoryService.Intraestructure\InventoryService.Intraestructure.csproj", "{8B9A75A4-1A4D-43F4-9EBB-A8A5937452B4}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Intraestructure", "InventoryService\src\InventoryService.Intraestructure\InventoryService.Intraestructure.csproj", "{D9BF7615-A829-4CE4-A790-DB79F6C0595A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Api", "InventoryService\src\InventoryService.Api\InventoryService.Api.csproj", "{E91C4C31-2C6C-4100-9388-B35857919DF6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Api", "InventoryService\src\InventoryService.Api\InventoryService.Api.csproj", "{9839B241-AB3D-4E9B-B41C-DE3B8DD93942}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Commons", "InventoryService\src\InventoryService.Commons\InventoryService.Commons.csproj", "{F40D7457-0273-4B86-AA99-C313BFB6F6E7}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Commons", "InventoryService\src\InventoryService.Commons\InventoryService.Commons.csproj", "{D3CC172A-6019-4202-8AF3-7B9B979F2760}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Domain", "InventoryService\src\InventoryService.Domain\InventoryService.Domain.csproj", "{0A9442B1-53A1-4B26-B033-A893ADF6BCEE}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Domain", "InventoryService\src\InventoryService.Domain\InventoryService.Domain.csproj", "{40BAFDCF-0608-4668-B73F-FC542F1CFD26}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Application", "InventoryService\src\InventoryService.Application\InventoryService.Application.csproj", "{8C094A65-8558-4A18-8BBC-DBD1DD35A686}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InventoryService.Application", "InventoryService\src\InventoryService.Application\InventoryService.Application.csproj", "{C0B6DF50-9333-49AF-9142-51974051630C}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NotificationService", "NotificationService", "{A4C9FED3-73F2-4A05-91BC-142B785F3950}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NotificationService", "NotificationService", "{079C8990-4FA9-468B-8067-79E100741DE8}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3117A4F1-7B05-45FC-A0E1-11230D16C81E}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D735030A-0EF5-4CB5-809B-FF434BCEDA4F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationServiceTests", "NotificationService\test\NotificationServiceTests\NotificationServiceTests.csproj", "{02157519-DF43-4CF7-BE6B-ED2B4FF80AE4}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationServiceTests", "NotificationService\test\NotificationServiceTests\NotificationServiceTests.csproj", "{F2F21161-AC70-4D04-AE19-6A601745E59C}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0400C5C6-540C-4605-B88F-31F33B7FFB86}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FD01570B-0EA5-4B29-8E23-FA1E55AE892B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Domain", "NotificationService\src\NotificationService.Domain\NotificationService.Domain.csproj", "{B179A8B9-504C-477D-90FA-ACFF227324CE}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Domain", "NotificationService\src\NotificationService.Domain\NotificationService.Domain.csproj", "{8B2020A5-F742-4467-BAAC-B7C6257FE9F6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Api", "NotificationService\src\NotificationService.Api\NotificationService.Api.csproj", "{4F58EDA0-3365-4FF6-9BE3-6C9F08557899}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Api", "NotificationService\src\NotificationService.Api\NotificationService.Api.csproj", "{8F253C80-A291-4D9E-9B6D-AB6EB4BB7CFA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Application", "NotificationService\src\NotificationService.Application\NotificationService.Application.csproj", "{4A754162-490A-4F15-8F0B-899825BF81E9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Application", "NotificationService\src\NotificationService.Application\NotificationService.Application.csproj", "{2C5F0523-5D96-4CF2-9484-817FB00B86C6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Infraestructure", "NotificationService\src\NotificationService.Infraestructure\NotificationService.Infraestructure.csproj", "{5C3CD3EA-1A14-4858-AC9D-A9FBF9AB37FA}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationService.Infraestructure", "NotificationService\src\NotificationService.Infraestructure\NotificationService.Infraestructure.csproj", "{6F9FB955-89AE-484B-8720-334FF714A4A3}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserService", "UserService", "{0203A6FF-E1E7-4E8A-BB56-6378848CFA4B}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserService", "UserService", "{319BC3FC-1248-45FA-8534-0335BEA81214}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{33B15287-39C5-4A0B-A7C5-B27CD258DE2A}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E188FFD0-FEA7-4313-8974-5F1363B207E6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserServiceTests", "UserService\test\UserServiceTests\UserServiceTests.csproj", "{EEF9B93B-ABB4-41EE-BF16-08F472813D92}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserServiceTests", "UserService\test\UserServiceTests\UserServiceTests.csproj", "{A7E1CE21-AA1C-48C6-BA21-81F61AD00F31}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{91E2E3B6-E17D-4F53-9424-0D7AD55C49D3}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D85044CE-2EFE-4A9D-9B6E-7616B852EFA6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Infrastructure", "UserService\src\UserService.Infrastructure\UserService.Infrastructure.csproj", "{382E5761-DBC8-4729-ABC1-358FF4DE1731}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Infrastructure", "UserService\src\UserService.Infrastructure\UserService.Infrastructure.csproj", "{FA6C583D-261E-4E7D-9D03-9C3AE0DB1134}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Domain", "UserService\src\UserService.Domain\UserService.Domain.csproj", "{439B1504-61A7-497B-9145-BAE6FA303100}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Domain", "UserService\src\UserService.Domain\UserService.Domain.csproj", "{A0E551F7-D4C3-4A0F-AD69-C1CA4AC3F88B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Application", "UserService\src\UserService.Application\UserService.Application.csproj", "{001F148D-8AC4-4294-B45D-8480C111FA75}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Application", "UserService\src\UserService.Application\UserService.Application.csproj", "{E1896DA1-3A31-44A1-8304-AD52A1632070}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Api", "UserService\src\UserService.Api\UserService.Api.csproj", "{47E5DFF9-CC6D-41CC-9953-5EA6A03E7E2E}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Api", "UserService\src\UserService.Api\UserService.Api.csproj", "{E6447958-3E7D-4FB7-8185-20B5497F5FB5}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReviewService", "ReviewService", "{E289E7B1-492E-412E-A093-4577015078EF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4129CE38-CE3C-473E-9E66-3F5729896349}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReviewService.Api", "ReviewService\src\ReviewService.Api\ReviewService.Api.csproj", "{3E11FBD4-DA05-4418-9AAA-BAE7E0069A26}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReviewService.Application", "ReviewService\src\ReviewService.Application\ReviewService.Application.csproj", "{9B34679B-ED3F-45B5-A532-FDE38EFF3E62}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReviewService.Domain", "ReviewService\src\ReviewService.Domain\ReviewService.Domain.csproj", "{0A9EF588-5765-4097-A235-C3427BFF6A6D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReviewService.Infrastructure", "ReviewService\src\ReviewService.Infrastructure\ReviewService.Infrastructure.csproj", "{AB0DC86C-1020-4EF7-9286-2367CD0F5A9D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C1F05FCE-5D03-40D9-A714-2993DDBAE03F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "ReviewService\test\TestProject1\TestProject1.csproj", "{33986658-70F9-4A3F-9061-2B0E668CEEB6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -81,139 +95,161 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {72FEA5ED-F129-4180-AEB1-B635528E5773}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {72FEA5ED-F129-4180-AEB1-B635528E5773}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {72FEA5ED-F129-4180-AEB1-B635528E5773}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {72FEA5ED-F129-4180-AEB1-B635528E5773}.Release|Any CPU.Build.0 = Release|Any CPU
- {C7D3696D-4B48-465E-AEE9-5124ACED816F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C7D3696D-4B48-465E-AEE9-5124ACED816F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C7D3696D-4B48-465E-AEE9-5124ACED816F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C7D3696D-4B48-465E-AEE9-5124ACED816F}.Release|Any CPU.Build.0 = Release|Any CPU
- {641D5831-29F7-4E30-ADA3-2B278479F4C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {641D5831-29F7-4E30-ADA3-2B278479F4C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {641D5831-29F7-4E30-ADA3-2B278479F4C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {641D5831-29F7-4E30-ADA3-2B278479F4C3}.Release|Any CPU.Build.0 = Release|Any CPU
- {3696BCB5-5C0A-4060-B079-579A9D8D2A6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3696BCB5-5C0A-4060-B079-579A9D8D2A6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3696BCB5-5C0A-4060-B079-579A9D8D2A6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3696BCB5-5C0A-4060-B079-579A9D8D2A6E}.Release|Any CPU.Build.0 = Release|Any CPU
- {AA82A51C-5725-44D4-9265-6A05F8AC4DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AA82A51C-5725-44D4-9265-6A05F8AC4DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AA82A51C-5725-44D4-9265-6A05F8AC4DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA82A51C-5725-44D4-9265-6A05F8AC4DD2}.Release|Any CPU.Build.0 = Release|Any CPU
- {1DFAC371-9AF7-427B-9483-2A5140EBB620}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1DFAC371-9AF7-427B-9483-2A5140EBB620}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1DFAC371-9AF7-427B-9483-2A5140EBB620}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1DFAC371-9AF7-427B-9483-2A5140EBB620}.Release|Any CPU.Build.0 = Release|Any CPU
- {B08D111D-B003-419C-AE8A-145FE1571105}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B08D111D-B003-419C-AE8A-145FE1571105}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B08D111D-B003-419C-AE8A-145FE1571105}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B08D111D-B003-419C-AE8A-145FE1571105}.Release|Any CPU.Build.0 = Release|Any CPU
- {46F79B66-C7ED-4F03-9169-BB7FC5F9F552}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {46F79B66-C7ED-4F03-9169-BB7FC5F9F552}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {46F79B66-C7ED-4F03-9169-BB7FC5F9F552}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {46F79B66-C7ED-4F03-9169-BB7FC5F9F552}.Release|Any CPU.Build.0 = Release|Any CPU
- {2D0FCE69-11D5-410C-9879-B3969C4BCF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2D0FCE69-11D5-410C-9879-B3969C4BCF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2D0FCE69-11D5-410C-9879-B3969C4BCF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2D0FCE69-11D5-410C-9879-B3969C4BCF9A}.Release|Any CPU.Build.0 = Release|Any CPU
- {D9BF7615-A829-4CE4-A790-DB79F6C0595A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D9BF7615-A829-4CE4-A790-DB79F6C0595A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D9BF7615-A829-4CE4-A790-DB79F6C0595A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D9BF7615-A829-4CE4-A790-DB79F6C0595A}.Release|Any CPU.Build.0 = Release|Any CPU
- {9839B241-AB3D-4E9B-B41C-DE3B8DD93942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9839B241-AB3D-4E9B-B41C-DE3B8DD93942}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9839B241-AB3D-4E9B-B41C-DE3B8DD93942}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9839B241-AB3D-4E9B-B41C-DE3B8DD93942}.Release|Any CPU.Build.0 = Release|Any CPU
- {D3CC172A-6019-4202-8AF3-7B9B979F2760}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D3CC172A-6019-4202-8AF3-7B9B979F2760}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D3CC172A-6019-4202-8AF3-7B9B979F2760}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D3CC172A-6019-4202-8AF3-7B9B979F2760}.Release|Any CPU.Build.0 = Release|Any CPU
- {40BAFDCF-0608-4668-B73F-FC542F1CFD26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {40BAFDCF-0608-4668-B73F-FC542F1CFD26}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {40BAFDCF-0608-4668-B73F-FC542F1CFD26}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {40BAFDCF-0608-4668-B73F-FC542F1CFD26}.Release|Any CPU.Build.0 = Release|Any CPU
- {C0B6DF50-9333-49AF-9142-51974051630C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C0B6DF50-9333-49AF-9142-51974051630C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C0B6DF50-9333-49AF-9142-51974051630C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C0B6DF50-9333-49AF-9142-51974051630C}.Release|Any CPU.Build.0 = Release|Any CPU
- {F2F21161-AC70-4D04-AE19-6A601745E59C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F2F21161-AC70-4D04-AE19-6A601745E59C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F2F21161-AC70-4D04-AE19-6A601745E59C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F2F21161-AC70-4D04-AE19-6A601745E59C}.Release|Any CPU.Build.0 = Release|Any CPU
- {8B2020A5-F742-4467-BAAC-B7C6257FE9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8B2020A5-F742-4467-BAAC-B7C6257FE9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8B2020A5-F742-4467-BAAC-B7C6257FE9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8B2020A5-F742-4467-BAAC-B7C6257FE9F6}.Release|Any CPU.Build.0 = Release|Any CPU
- {8F253C80-A291-4D9E-9B6D-AB6EB4BB7CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8F253C80-A291-4D9E-9B6D-AB6EB4BB7CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8F253C80-A291-4D9E-9B6D-AB6EB4BB7CFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8F253C80-A291-4D9E-9B6D-AB6EB4BB7CFA}.Release|Any CPU.Build.0 = Release|Any CPU
- {2C5F0523-5D96-4CF2-9484-817FB00B86C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2C5F0523-5D96-4CF2-9484-817FB00B86C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2C5F0523-5D96-4CF2-9484-817FB00B86C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2C5F0523-5D96-4CF2-9484-817FB00B86C6}.Release|Any CPU.Build.0 = Release|Any CPU
- {6F9FB955-89AE-484B-8720-334FF714A4A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6F9FB955-89AE-484B-8720-334FF714A4A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6F9FB955-89AE-484B-8720-334FF714A4A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6F9FB955-89AE-484B-8720-334FF714A4A3}.Release|Any CPU.Build.0 = Release|Any CPU
- {A7E1CE21-AA1C-48C6-BA21-81F61AD00F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A7E1CE21-AA1C-48C6-BA21-81F61AD00F31}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A7E1CE21-AA1C-48C6-BA21-81F61AD00F31}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A7E1CE21-AA1C-48C6-BA21-81F61AD00F31}.Release|Any CPU.Build.0 = Release|Any CPU
- {FA6C583D-261E-4E7D-9D03-9C3AE0DB1134}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FA6C583D-261E-4E7D-9D03-9C3AE0DB1134}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FA6C583D-261E-4E7D-9D03-9C3AE0DB1134}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FA6C583D-261E-4E7D-9D03-9C3AE0DB1134}.Release|Any CPU.Build.0 = Release|Any CPU
- {A0E551F7-D4C3-4A0F-AD69-C1CA4AC3F88B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A0E551F7-D4C3-4A0F-AD69-C1CA4AC3F88B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A0E551F7-D4C3-4A0F-AD69-C1CA4AC3F88B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A0E551F7-D4C3-4A0F-AD69-C1CA4AC3F88B}.Release|Any CPU.Build.0 = Release|Any CPU
- {E1896DA1-3A31-44A1-8304-AD52A1632070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E1896DA1-3A31-44A1-8304-AD52A1632070}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E1896DA1-3A31-44A1-8304-AD52A1632070}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E1896DA1-3A31-44A1-8304-AD52A1632070}.Release|Any CPU.Build.0 = Release|Any CPU
- {E6447958-3E7D-4FB7-8185-20B5497F5FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E6447958-3E7D-4FB7-8185-20B5497F5FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E6447958-3E7D-4FB7-8185-20B5497F5FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E6447958-3E7D-4FB7-8185-20B5497F5FB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1071D398-B0D1-4C77-AC18-E8E2792A7B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1071D398-B0D1-4C77-AC18-E8E2792A7B38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1071D398-B0D1-4C77-AC18-E8E2792A7B38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1071D398-B0D1-4C77-AC18-E8E2792A7B38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E740634-8267-4EA5-9C18-9C89487E7A0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E740634-8267-4EA5-9C18-9C89487E7A0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E740634-8267-4EA5-9C18-9C89487E7A0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E740634-8267-4EA5-9C18-9C89487E7A0F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {86D9DEF0-38DC-4559-8E1F-93687B4B9291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {86D9DEF0-38DC-4559-8E1F-93687B4B9291}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {86D9DEF0-38DC-4559-8E1F-93687B4B9291}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {86D9DEF0-38DC-4559-8E1F-93687B4B9291}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9A6CD5DF-72C7-4997-894A-EE2294EE1917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9A6CD5DF-72C7-4997-894A-EE2294EE1917}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A6CD5DF-72C7-4997-894A-EE2294EE1917}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9A6CD5DF-72C7-4997-894A-EE2294EE1917}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF0F7F3F-08A8-4393-8846-900BC1D518B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF0F7F3F-08A8-4393-8846-900BC1D518B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF0F7F3F-08A8-4393-8846-900BC1D518B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF0F7F3F-08A8-4393-8846-900BC1D518B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F75D64C-9B03-4FC1-A46E-11F6A5881EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F75D64C-9B03-4FC1-A46E-11F6A5881EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F75D64C-9B03-4FC1-A46E-11F6A5881EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F75D64C-9B03-4FC1-A46E-11F6A5881EE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8A61614-D1C9-4EB2-979B-A9AABDBBCD58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8A61614-D1C9-4EB2-979B-A9AABDBBCD58}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8A61614-D1C9-4EB2-979B-A9AABDBBCD58}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8A61614-D1C9-4EB2-979B-A9AABDBBCD58}.Release|Any CPU.Build.0 = Release|Any CPU
+ {35374A82-8786-4817-A4F1-1CF317C16597}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {35374A82-8786-4817-A4F1-1CF317C16597}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35374A82-8786-4817-A4F1-1CF317C16597}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {35374A82-8786-4817-A4F1-1CF317C16597}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B9A75A4-1A4D-43F4-9EBB-A8A5937452B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B9A75A4-1A4D-43F4-9EBB-A8A5937452B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B9A75A4-1A4D-43F4-9EBB-A8A5937452B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B9A75A4-1A4D-43F4-9EBB-A8A5937452B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E91C4C31-2C6C-4100-9388-B35857919DF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E91C4C31-2C6C-4100-9388-B35857919DF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E91C4C31-2C6C-4100-9388-B35857919DF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E91C4C31-2C6C-4100-9388-B35857919DF6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F40D7457-0273-4B86-AA99-C313BFB6F6E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F40D7457-0273-4B86-AA99-C313BFB6F6E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F40D7457-0273-4B86-AA99-C313BFB6F6E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F40D7457-0273-4B86-AA99-C313BFB6F6E7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0A9442B1-53A1-4B26-B033-A893ADF6BCEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0A9442B1-53A1-4B26-B033-A893ADF6BCEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0A9442B1-53A1-4B26-B033-A893ADF6BCEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0A9442B1-53A1-4B26-B033-A893ADF6BCEE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C094A65-8558-4A18-8BBC-DBD1DD35A686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C094A65-8558-4A18-8BBC-DBD1DD35A686}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C094A65-8558-4A18-8BBC-DBD1DD35A686}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C094A65-8558-4A18-8BBC-DBD1DD35A686}.Release|Any CPU.Build.0 = Release|Any CPU
+ {02157519-DF43-4CF7-BE6B-ED2B4FF80AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {02157519-DF43-4CF7-BE6B-ED2B4FF80AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {02157519-DF43-4CF7-BE6B-ED2B4FF80AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {02157519-DF43-4CF7-BE6B-ED2B4FF80AE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B179A8B9-504C-477D-90FA-ACFF227324CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B179A8B9-504C-477D-90FA-ACFF227324CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B179A8B9-504C-477D-90FA-ACFF227324CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B179A8B9-504C-477D-90FA-ACFF227324CE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4F58EDA0-3365-4FF6-9BE3-6C9F08557899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F58EDA0-3365-4FF6-9BE3-6C9F08557899}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F58EDA0-3365-4FF6-9BE3-6C9F08557899}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F58EDA0-3365-4FF6-9BE3-6C9F08557899}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4A754162-490A-4F15-8F0B-899825BF81E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4A754162-490A-4F15-8F0B-899825BF81E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4A754162-490A-4F15-8F0B-899825BF81E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4A754162-490A-4F15-8F0B-899825BF81E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C3CD3EA-1A14-4858-AC9D-A9FBF9AB37FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C3CD3EA-1A14-4858-AC9D-A9FBF9AB37FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C3CD3EA-1A14-4858-AC9D-A9FBF9AB37FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C3CD3EA-1A14-4858-AC9D-A9FBF9AB37FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EEF9B93B-ABB4-41EE-BF16-08F472813D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EEF9B93B-ABB4-41EE-BF16-08F472813D92}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EEF9B93B-ABB4-41EE-BF16-08F472813D92}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EEF9B93B-ABB4-41EE-BF16-08F472813D92}.Release|Any CPU.Build.0 = Release|Any CPU
+ {382E5761-DBC8-4729-ABC1-358FF4DE1731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {382E5761-DBC8-4729-ABC1-358FF4DE1731}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {382E5761-DBC8-4729-ABC1-358FF4DE1731}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {382E5761-DBC8-4729-ABC1-358FF4DE1731}.Release|Any CPU.Build.0 = Release|Any CPU
+ {439B1504-61A7-497B-9145-BAE6FA303100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {439B1504-61A7-497B-9145-BAE6FA303100}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {439B1504-61A7-497B-9145-BAE6FA303100}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {439B1504-61A7-497B-9145-BAE6FA303100}.Release|Any CPU.Build.0 = Release|Any CPU
+ {001F148D-8AC4-4294-B45D-8480C111FA75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {001F148D-8AC4-4294-B45D-8480C111FA75}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {001F148D-8AC4-4294-B45D-8480C111FA75}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {001F148D-8AC4-4294-B45D-8480C111FA75}.Release|Any CPU.Build.0 = Release|Any CPU
+ {47E5DFF9-CC6D-41CC-9953-5EA6A03E7E2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {47E5DFF9-CC6D-41CC-9953-5EA6A03E7E2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47E5DFF9-CC6D-41CC-9953-5EA6A03E7E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {47E5DFF9-CC6D-41CC-9953-5EA6A03E7E2E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E11FBD4-DA05-4418-9AAA-BAE7E0069A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E11FBD4-DA05-4418-9AAA-BAE7E0069A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E11FBD4-DA05-4418-9AAA-BAE7E0069A26}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E11FBD4-DA05-4418-9AAA-BAE7E0069A26}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9B34679B-ED3F-45B5-A532-FDE38EFF3E62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9B34679B-ED3F-45B5-A532-FDE38EFF3E62}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9B34679B-ED3F-45B5-A532-FDE38EFF3E62}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9B34679B-ED3F-45B5-A532-FDE38EFF3E62}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0A9EF588-5765-4097-A235-C3427BFF6A6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0A9EF588-5765-4097-A235-C3427BFF6A6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0A9EF588-5765-4097-A235-C3427BFF6A6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0A9EF588-5765-4097-A235-C3427BFF6A6D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB0DC86C-1020-4EF7-9286-2367CD0F5A9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB0DC86C-1020-4EF7-9286-2367CD0F5A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB0DC86C-1020-4EF7-9286-2367CD0F5A9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB0DC86C-1020-4EF7-9286-2367CD0F5A9D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {33986658-70F9-4A3F-9061-2B0E668CEEB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {33986658-70F9-4A3F-9061-2B0E668CEEB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {33986658-70F9-4A3F-9061-2B0E668CEEB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {33986658-70F9-4A3F-9061-2B0E668CEEB6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {C7D3696D-4B48-465E-AEE9-5124ACED816F} = {E65BEF82-5698-437E-847B-FFA6BD340DB2}
- {641D5831-29F7-4E30-ADA3-2B278479F4C3} = {E65BEF82-5698-437E-847B-FFA6BD340DB2}
- {EC25300E-BE2E-4987-B7A1-AC8AA9380F2D} = {D4DE45EE-E027-43ED-BB50-BF5136664DD4}
- {3696BCB5-5C0A-4060-B079-579A9D8D2A6E} = {EC25300E-BE2E-4987-B7A1-AC8AA9380F2D}
- {AA82A51C-5725-44D4-9265-6A05F8AC4DD2} = {EC25300E-BE2E-4987-B7A1-AC8AA9380F2D}
- {1DFAC371-9AF7-427B-9483-2A5140EBB620} = {EC25300E-BE2E-4987-B7A1-AC8AA9380F2D}
- {B08D111D-B003-419C-AE8A-145FE1571105} = {EC25300E-BE2E-4987-B7A1-AC8AA9380F2D}
- {46F79B66-C7ED-4F03-9169-BB7FC5F9F552} = {EC25300E-BE2E-4987-B7A1-AC8AA9380F2D}
- {57E1E4D9-98FE-424E-AC8D-47C50537CDC8} = {B72BE68D-0106-46A6-A281-D912E90703C3}
- {2D0FCE69-11D5-410C-9879-B3969C4BCF9A} = {57E1E4D9-98FE-424E-AC8D-47C50537CDC8}
- {00EE6191-1D03-460D-A6B8-41C5A13C8CBE} = {B72BE68D-0106-46A6-A281-D912E90703C3}
- {D9BF7615-A829-4CE4-A790-DB79F6C0595A} = {00EE6191-1D03-460D-A6B8-41C5A13C8CBE}
- {9839B241-AB3D-4E9B-B41C-DE3B8DD93942} = {00EE6191-1D03-460D-A6B8-41C5A13C8CBE}
- {D3CC172A-6019-4202-8AF3-7B9B979F2760} = {00EE6191-1D03-460D-A6B8-41C5A13C8CBE}
- {40BAFDCF-0608-4668-B73F-FC542F1CFD26} = {00EE6191-1D03-460D-A6B8-41C5A13C8CBE}
- {C0B6DF50-9333-49AF-9142-51974051630C} = {00EE6191-1D03-460D-A6B8-41C5A13C8CBE}
- {D735030A-0EF5-4CB5-809B-FF434BCEDA4F} = {079C8990-4FA9-468B-8067-79E100741DE8}
- {F2F21161-AC70-4D04-AE19-6A601745E59C} = {D735030A-0EF5-4CB5-809B-FF434BCEDA4F}
- {FD01570B-0EA5-4B29-8E23-FA1E55AE892B} = {079C8990-4FA9-468B-8067-79E100741DE8}
- {8B2020A5-F742-4467-BAAC-B7C6257FE9F6} = {FD01570B-0EA5-4B29-8E23-FA1E55AE892B}
- {8F253C80-A291-4D9E-9B6D-AB6EB4BB7CFA} = {FD01570B-0EA5-4B29-8E23-FA1E55AE892B}
- {2C5F0523-5D96-4CF2-9484-817FB00B86C6} = {FD01570B-0EA5-4B29-8E23-FA1E55AE892B}
- {6F9FB955-89AE-484B-8720-334FF714A4A3} = {FD01570B-0EA5-4B29-8E23-FA1E55AE892B}
- {E188FFD0-FEA7-4313-8974-5F1363B207E6} = {319BC3FC-1248-45FA-8534-0335BEA81214}
- {A7E1CE21-AA1C-48C6-BA21-81F61AD00F31} = {E188FFD0-FEA7-4313-8974-5F1363B207E6}
- {D85044CE-2EFE-4A9D-9B6E-7616B852EFA6} = {319BC3FC-1248-45FA-8534-0335BEA81214}
- {FA6C583D-261E-4E7D-9D03-9C3AE0DB1134} = {D85044CE-2EFE-4A9D-9B6E-7616B852EFA6}
- {A0E551F7-D4C3-4A0F-AD69-C1CA4AC3F88B} = {D85044CE-2EFE-4A9D-9B6E-7616B852EFA6}
- {E1896DA1-3A31-44A1-8304-AD52A1632070} = {D85044CE-2EFE-4A9D-9B6E-7616B852EFA6}
- {E6447958-3E7D-4FB7-8185-20B5497F5FB5} = {D85044CE-2EFE-4A9D-9B6E-7616B852EFA6}
+ {2E740634-8267-4EA5-9C18-9C89487E7A0F} = {8D623CEF-6CAA-4220-A85D-C1768B1602B4}
+ {86D9DEF0-38DC-4559-8E1F-93687B4B9291} = {8D623CEF-6CAA-4220-A85D-C1768B1602B4}
+ {D8A3298A-2E52-4DE1-84B2-97D2ED1A3680} = {A91C63E4-FAE1-4D99-9BD0-880A5202BAD3}
+ {9A6CD5DF-72C7-4997-894A-EE2294EE1917} = {D8A3298A-2E52-4DE1-84B2-97D2ED1A3680}
+ {DF0F7F3F-08A8-4393-8846-900BC1D518B0} = {D8A3298A-2E52-4DE1-84B2-97D2ED1A3680}
+ {3F75D64C-9B03-4FC1-A46E-11F6A5881EE7} = {D8A3298A-2E52-4DE1-84B2-97D2ED1A3680}
+ {F8A61614-D1C9-4EB2-979B-A9AABDBBCD58} = {D8A3298A-2E52-4DE1-84B2-97D2ED1A3680}
+ {4B66FB5F-B6AC-4097-BFFA-C613967F34EC} = {50C4F989-1DC5-4085-B0DA-6ABE54D57CE3}
+ {35374A82-8786-4817-A4F1-1CF317C16597} = {4B66FB5F-B6AC-4097-BFFA-C613967F34EC}
+ {613127B5-4792-4540-A58F-A415ED4A4BC9} = {50C4F989-1DC5-4085-B0DA-6ABE54D57CE3}
+ {8B9A75A4-1A4D-43F4-9EBB-A8A5937452B4} = {613127B5-4792-4540-A58F-A415ED4A4BC9}
+ {E91C4C31-2C6C-4100-9388-B35857919DF6} = {613127B5-4792-4540-A58F-A415ED4A4BC9}
+ {F40D7457-0273-4B86-AA99-C313BFB6F6E7} = {613127B5-4792-4540-A58F-A415ED4A4BC9}
+ {0A9442B1-53A1-4B26-B033-A893ADF6BCEE} = {613127B5-4792-4540-A58F-A415ED4A4BC9}
+ {8C094A65-8558-4A18-8BBC-DBD1DD35A686} = {613127B5-4792-4540-A58F-A415ED4A4BC9}
+ {3117A4F1-7B05-45FC-A0E1-11230D16C81E} = {A4C9FED3-73F2-4A05-91BC-142B785F3950}
+ {02157519-DF43-4CF7-BE6B-ED2B4FF80AE4} = {3117A4F1-7B05-45FC-A0E1-11230D16C81E}
+ {0400C5C6-540C-4605-B88F-31F33B7FFB86} = {A4C9FED3-73F2-4A05-91BC-142B785F3950}
+ {B179A8B9-504C-477D-90FA-ACFF227324CE} = {0400C5C6-540C-4605-B88F-31F33B7FFB86}
+ {4F58EDA0-3365-4FF6-9BE3-6C9F08557899} = {0400C5C6-540C-4605-B88F-31F33B7FFB86}
+ {4A754162-490A-4F15-8F0B-899825BF81E9} = {0400C5C6-540C-4605-B88F-31F33B7FFB86}
+ {5C3CD3EA-1A14-4858-AC9D-A9FBF9AB37FA} = {0400C5C6-540C-4605-B88F-31F33B7FFB86}
+ {33B15287-39C5-4A0B-A7C5-B27CD258DE2A} = {0203A6FF-E1E7-4E8A-BB56-6378848CFA4B}
+ {EEF9B93B-ABB4-41EE-BF16-08F472813D92} = {33B15287-39C5-4A0B-A7C5-B27CD258DE2A}
+ {91E2E3B6-E17D-4F53-9424-0D7AD55C49D3} = {0203A6FF-E1E7-4E8A-BB56-6378848CFA4B}
+ {382E5761-DBC8-4729-ABC1-358FF4DE1731} = {91E2E3B6-E17D-4F53-9424-0D7AD55C49D3}
+ {439B1504-61A7-497B-9145-BAE6FA303100} = {91E2E3B6-E17D-4F53-9424-0D7AD55C49D3}
+ {001F148D-8AC4-4294-B45D-8480C111FA75} = {91E2E3B6-E17D-4F53-9424-0D7AD55C49D3}
+ {47E5DFF9-CC6D-41CC-9953-5EA6A03E7E2E} = {91E2E3B6-E17D-4F53-9424-0D7AD55C49D3}
+ {4129CE38-CE3C-473E-9E66-3F5729896349} = {E289E7B1-492E-412E-A093-4577015078EF}
+ {3E11FBD4-DA05-4418-9AAA-BAE7E0069A26} = {4129CE38-CE3C-473E-9E66-3F5729896349}
+ {9B34679B-ED3F-45B5-A532-FDE38EFF3E62} = {4129CE38-CE3C-473E-9E66-3F5729896349}
+ {0A9EF588-5765-4097-A235-C3427BFF6A6D} = {4129CE38-CE3C-473E-9E66-3F5729896349}
+ {AB0DC86C-1020-4EF7-9286-2367CD0F5A9D} = {4129CE38-CE3C-473E-9E66-3F5729896349}
+ {C1F05FCE-5D03-40D9-A714-2993DDBAE03F} = {E289E7B1-492E-412E-A093-4577015078EF}
+ {33986658-70F9-4A3F-9061-2B0E668CEEB6} = {C1F05FCE-5D03-40D9-A714-2993DDBAE03F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {CEEB046B-CF4C-42DB-9AD3-6F50A0D68CC1}
+ SolutionGuid = {E1CFB179-5B18-4F5E-817A-3C705DD3773A}
EndGlobalSection
EndGlobal
diff --git a/apps/merchant-web/src/app/page.tsx b/apps/merchant-web/src/app/page.tsx
index d304d0c8..a762311c 100644
--- a/apps/merchant-web/src/app/page.tsx
+++ b/apps/merchant-web/src/app/page.tsx
@@ -33,9 +33,9 @@ export default function Home() {
product.brand,
product.isLiked,
product.images,
+ product.rating,
product.productVariants,
product.categories,
- product.productReviews
)
)
);
diff --git a/apps/merchant-web/src/app/product-details/[id]/page.tsx b/apps/merchant-web/src/app/product-details/[id]/page.tsx
index 7bbd4a37..91341966 100644
--- a/apps/merchant-web/src/app/product-details/[id]/page.tsx
+++ b/apps/merchant-web/src/app/product-details/[id]/page.tsx
@@ -8,6 +8,7 @@ import axiosInstance from "@/request/AxiosConfig";
import { ShoppingItemProvider } from "@/contexts/ShoppingItemContext";
import { ProductDetail } from "@/components/product-detail/ProductDetail";
+import { ReviewsProvider } from "@/contexts/ReviewsContext";
export default function ProductDetails() {
const params = useParams();
@@ -34,7 +35,9 @@ export default function ProductDetails() {
return product ? (
-
+
+
+
) : (
Loading product details...
diff --git a/apps/merchant-web/src/commons/entities/concretes/Product.ts b/apps/merchant-web/src/commons/entities/concretes/Product.ts
index a1f398d3..2c2673db 100644
--- a/apps/merchant-web/src/commons/entities/concretes/Product.ts
+++ b/apps/merchant-web/src/commons/entities/concretes/Product.ts
@@ -2,7 +2,6 @@ import { UUID } from "crypto";
import ProductVariant from "./ProductVariant";
import Category from "./Category";
import Image from "./Image";
-import ProductReview from "./ProductReview";
import {boolean} from "zod";
@@ -15,9 +14,9 @@ export default class Product {
brand: string;
isLiked: boolean;
images: Array;
+ rating: number;
productVariants: Array;
categories: Array;
- productReviews: Array;
lowStockThreshold?: number;
constructor(
@@ -29,9 +28,9 @@ export default class Product {
brand: string,
isLiked: boolean,
images: Array,
+ rating: number,
productVariants: Array,
categories: Array,
- productReviews: Array,
lowStockThreshold?: number
) {
this.id = id;
@@ -43,8 +42,8 @@ export default class Product {
this.brand = brand;
this.categories = categories;
this.images = images;
+ this.rating = rating;
this.productVariants = productVariants;
- this.productReviews = productReviews;
this.categories = categories;
this.lowStockThreshold = lowStockThreshold;
}
diff --git a/apps/merchant-web/src/components/RatingSelector.tsx b/apps/merchant-web/src/components/RatingSelector.tsx
index 4d8c8fbc..e0afe801 100644
--- a/apps/merchant-web/src/components/RatingSelector.tsx
+++ b/apps/merchant-web/src/components/RatingSelector.tsx
@@ -4,67 +4,132 @@ import {
MdOutlineStarBorder,
MdOutlineStarHalf,
} from "react-icons/md";
-import { useState } from "react";
+import {useState} from "react";
interface RatingSelectorProps {
rating: number;
+ setRating: (number) => void;
+ handleChange?: () => void;
+ showTotalInfo?: boolean;
+ totalReviews?: number;
+ horizontalAlignment?: boolean;
+ isEditable?: boolean;
}
export default function RatingSelector({
- rating,
+ rating, handleChange, setRating, showTotalInfo = true, totalReviews, horizontalAlignment = true, isEditable = true
}: Readonly) {
- const [currentRating, setCurrentRating] = useState(rating);
const size = 24;
const color = "#FFC633";
- const fullStars = Math.floor(currentRating);
- const hasHalfStar = currentRating % 1 !== 0;
+ const [hoverRating, setHoverRating] = useState(0);
- const handleStarClick = (index: number) => {
- const newRating = index + 1;
- setCurrentRating(newRating);
+ const fullStars = Math.floor(hoverRating || rating);
+ const hasHalfStar = (hoverRating || rating) % 1 !== 0;
+
+ const handleStarClick = (index) => {
+ if (isEditable) {
+ const newRating = index + 1;
+ setRating(newRating);
+ }
+ };
+
+ const handleMouseEnter = (index) => {
+ if (isEditable) setHoverRating(index + 1);
+ };
+
+ const handleMouseLeave = () => {
+ setHoverRating(0);
};
return (
-
- {Array.from({ length: 5 }).map((_, index) => {
- if (index < fullStars) {
- return (
-
handleStarClick(index)}
- />
- );
- } else if (index === fullStars && hasHalfStar) {
- return (
- handleStarClick(index)}
- />
- );
- } else {
- return (
- handleStarClick(index)}
- />
- );
- }
- })}
-
);
}
diff --git a/apps/merchant-web/src/components/catalog/ProductsView.tsx b/apps/merchant-web/src/components/catalog/ProductsView.tsx
index 1f4a2405..d52a9b06 100644
--- a/apps/merchant-web/src/components/catalog/ProductsView.tsx
+++ b/apps/merchant-web/src/components/catalog/ProductsView.tsx
@@ -51,9 +51,9 @@ export default function ProductsView({ fetchConfig, view }: Readonly) {
product.brand,
product.isLiked,
product.images,
+ product.rating,
product.productVariants,
product.categories,
- product.productReviews
)
)
);
diff --git a/apps/merchant-web/src/components/contact-us/ContactUsForm.tsx b/apps/merchant-web/src/components/contact-us/ContactUsForm.tsx
index c92a0b9a..7b96338f 100644
--- a/apps/merchant-web/src/components/contact-us/ContactUsForm.tsx
+++ b/apps/merchant-web/src/components/contact-us/ContactUsForm.tsx
@@ -8,17 +8,7 @@ import { ContactFormData } from "@/schemes/contact-us-form/ContactUsDto";
import styles from "@/styles/contact-us/ContactUsForm.module.css"
import {createContactUsMessage} from "@/request/ContactUsMessageRequest";
import {ContactFormSchema, defaultContactData} from "@/schemes/contact-us-form/ContactFormSchema";
-import { z } from "zod";
-
-const validateWithZod = (schema: z.ZodSchema) => (values: any) => {
- try {
- schema.parse(values);
- return {};
- } catch (error) {
- const zodErrors = (error as z.ZodError).flatten();
- return zodErrors.fieldErrors;
- }
-};
+import {validateWithZod} from "@/utils/ZodFunctions";
const ContactForm: React.FC = () => {
const formik: FormikProps = useFormik({
@@ -107,4 +97,4 @@ const ContactForm: React.FC = () => {
);
};
-export default ContactForm;
\ No newline at end of file
+export default ContactForm;
diff --git a/apps/merchant-web/src/components/general/ProductCard.tsx b/apps/merchant-web/src/components/general/ProductCard.tsx
index 0725cdd9..4e39ca55 100644
--- a/apps/merchant-web/src/components/general/ProductCard.tsx
+++ b/apps/merchant-web/src/components/general/ProductCard.tsx
@@ -72,7 +72,7 @@ export const ProductCard = ({ product, type, onDislike }: Props) => {
$ {product.price}
- 0
+ {product.rating}
diff --git a/apps/merchant-web/src/components/product-detail/ProductDetail.tsx b/apps/merchant-web/src/components/product-detail/ProductDetail.tsx
index 308623d6..3c6d2fa6 100644
--- a/apps/merchant-web/src/components/product-detail/ProductDetail.tsx
+++ b/apps/merchant-web/src/components/product-detail/ProductDetail.tsx
@@ -1,7 +1,6 @@
"use client";
import { useEffect, useState } from "react";
-import RatingSelector from "@/components/RatingSelector";
import ImagesSection, {
Image,
} from "@/components/product-details/ImagesSection";
@@ -13,6 +12,9 @@ import axiosInstance from "@/request/AxiosConfig";
import { useShoppingCart } from "@/contexts/ShoppingCartContext";
import { AttributeSelector } from "./AttributeSelector";
import styles from "@/styles/products/ProductDetails.module.css";
+import ReviewModal from "@/components/reviews/ReviewModal";
+import ReviewCard from "@/components/reviews/ReviewCard";
+import ReviewsSection from "@/components/product-detail/ReviewsSection";
export const ProductDetail = () => {
const [isFavorite, setIsFavorite] = useState(false);
@@ -107,62 +109,65 @@ export const ProductDetail = () => {
}
return (
-
-
-
- {product.name}
-
-
- $ {product.price}
-
- {" "}
- ({priceAdjustment > 0 ? `+ ${priceAdjustment} $` : ""})
-
-
- {price > 0 && Total: $ {price}}
- {product.description}
-
- {attributes && attributes.length > 0 ? (
- attributes.map((attribute) => {
- return (
-
-
Select {attribute.name}
-
{
- chooseAttribute(attribute.name, value);
- }}
- />
-
- );
- })
- ) : (
- No variants available
- )}
- {error && (
-
- *
- {error}
+
+
+
+
+ {product.name}
+
+
+ $ {product.price}
+
+ {" "}
+ ({priceAdjustment > 0 ? `+ ${priceAdjustment} $` : ""})
+
- )}
-
-
-
-
+ {price > 0 &&
Total: $ {price}}
+
{product.description}
+
+ {attributes && attributes.length > 0 ? (
+ attributes.map((attribute) => {
+ return (
+
+
Select {attribute.name}
+
{
+ chooseAttribute(attribute.name, value);
+ }}
+ />
+
+ );
+ })
+ ) : (
+
No variants available
+ )}
+ {error && (
+
+ *
+ {error}
+
+ )}
+
+
+
+
+
+
);
};
diff --git a/apps/merchant-web/src/components/product-detail/ReviewsSection.tsx b/apps/merchant-web/src/components/product-detail/ReviewsSection.tsx
new file mode 100644
index 00000000..22e661e8
--- /dev/null
+++ b/apps/merchant-web/src/components/product-detail/ReviewsSection.tsx
@@ -0,0 +1,27 @@
+import {useReviewsContext} from "@/contexts/ReviewsContext";
+import ReviewCard from "@/components/reviews/ReviewCard";
+import ReviewSectionStyle from "@/styles/reviews/ReviewSection.module.css"
+import PageSelector from "@/components/PageSelector";
+import ReviewModal from "@/components/reviews/ReviewModal";
+
+export default function ReviewsSection() {
+ const {reviews, isLoading, totalReviews, page, setPage} = useReviewsContext();
+ return (
+
+
+
All reviews ({totalReviews})
+
+
+
+ {reviews?.reviews.items.map((item, index) => (
+
+
+ ))}
+
+ {reviews == null && It looks like no one has shared their thoughts about this product yet. Why not be the first to leave a review and help others make their choice?
}
+ {reviews?.reviews.totalPages > 1 && }
+
+ )
+}
diff --git a/apps/merchant-web/src/components/reviews/ReviewCard.tsx b/apps/merchant-web/src/components/reviews/ReviewCard.tsx
new file mode 100644
index 00000000..4b3a4123
--- /dev/null
+++ b/apps/merchant-web/src/components/reviews/ReviewCard.tsx
@@ -0,0 +1,66 @@
+import RatingSelector from "@/components/RatingSelector";
+import ReviewCardStyle from "@/styles/reviews/ReviewsCard.module.css"
+import {Skeleton} from "@nextui-org/skeleton";
+import {FormatDateLiterary} from "@/utils/DateFormatter";
+
+interface Props {
+ review: Review
+ isLoading: boolean,
+}
+export default function ReviewCard({review, isLoading}: Readonly) {
+ const showSkeleton = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ const card = () => {
+ return (
+
+
{}}
+ showTotalInfo={false}>
+
+
{review.clientName}
+ {review.comment && review.comment.trim() !== "" && (
+
"{review.comment}"
+ )}
+
Posted on {FormatDateLiterary(review.createdAt)}
+
+ )
+ }
+
+ return isLoading ? showSkeleton() : card();
+}
diff --git a/apps/merchant-web/src/components/reviews/ReviewModal.tsx b/apps/merchant-web/src/components/reviews/ReviewModal.tsx
new file mode 100644
index 00000000..6a6ea4b0
--- /dev/null
+++ b/apps/merchant-web/src/components/reviews/ReviewModal.tsx
@@ -0,0 +1,136 @@
+import {Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, useDisclosure} from "@nextui-org/react";
+import {useState} from "react";
+import {toast} from "sonner";
+import {useRouter} from "next/navigation";
+import {FormikProps, useFormik} from "formik";
+import RatingSelector from "@/components/RatingSelector";
+import {EditableInput} from "@/components/atoms/inputs/EditableInput";
+import {defaultReviewFormData, ReviewFormData, ReviewFormSchema} from "@/schemes/reviews/ReviewFormSchema";
+import {validateWithZod} from "@/utils/ZodFunctions";
+import ModalStyle from "@/styles/store-catalog/Modal.module.css";
+import axiosInstance from "@/request/AxiosConfig";
+import {useAuth} from "@/commons/context/AuthContext";
+import {useReviewsContext} from "@/contexts/ReviewsContext";
+import ReviewSectionStyle from "@/styles/reviews/ReviewSection.module.css";
+
+interface Props {
+ type?: "button" | "rating-selector"
+}
+
+export default function ReviewModal({type = "rating-selector"}: Readonly) {
+ const [ratingForm, setRatingForm] = useState(0);
+ const {productId, rating, totalReviews, refreshReviews, isAbleToAdd} = useReviewsContext();
+ const {isOpen, onOpen, onOpenChange, onClose} = useDisclosure();
+ const auth = useAuth()
+ const router = useRouter();
+
+ const formik: FormikProps = useFormik({
+ initialValues: defaultReviewFormData,
+ validate: validateWithZod(ReviewFormSchema),
+ onSubmit: values => {
+ if (auth.user?.userId == null) {
+ router.replace("/login");
+ } else {
+ axiosInstance.post(`/review/ProductReview/${productId}`, {
+ clientId: auth.user?.userId,
+ clientName: auth.user?.displayName,
+ rating: ratingForm,
+ comment: values.comment
+ }).catch(e => {
+ console.log(e);
+ e.response.data.errors.map(error => {
+ toast.error(error);
+ })
+ toast.error(e.response.data.message);
+ })
+ .finally(() => {
+ refreshReviews();
+ onClose();
+ });
+ }
+ }
+ });
+
+ const getOpenType = () => {
+ switch (type) {
+ case "button":
+ return isAbleToAdd ? (
+ Add review
+ ) : <>>;
+ case "rating-selector":
+ return (
+ {}}
+ totalReviews={totalReviews}
+ horizontalAlignment={true}
+ showTotalInfo={true}
+ isEditable={isAbleToAdd}
+ >
+ );
+ default:
+ return (<>>);
+ }
+ };
+
+ return (
+ <>
+ {getOpenType()}
+
+
+
+ Add your review...
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/apps/merchant-web/src/contexts/ReviewsContext.tsx b/apps/merchant-web/src/contexts/ReviewsContext.tsx
new file mode 100644
index 00000000..b384c363
--- /dev/null
+++ b/apps/merchant-web/src/contexts/ReviewsContext.tsx
@@ -0,0 +1,97 @@
+import {createContext, ReactNode, useContext, useEffect, useMemo, useState} from "react";
+import axiosInstance from "@/request/AxiosConfig";
+import {toast} from "sonner";
+import {useAuth} from "@/commons/context/AuthContext";
+
+interface Type {
+ isAbleToAdd: boolean;
+ isLoading: boolean;
+ productId: string;
+ rating: number;
+ totalReviews: number;
+ reviews: Reviews | undefined;
+ page: number,
+ setRating: (number) => void;
+ refreshReviews: () => void;
+ setPage: (number) => void;
+ setIsLoading: (boolean) => void;
+ setIsAbleToAdd:(boolean) => void;
+}
+
+interface ReviewsProviderProps {
+ children: ReactNode;
+ productId: string;
+}
+
+export const ReviewsContext = createContext(undefined);
+
+export const useReviewsContext = () => {
+ const context = useContext(ReviewsContext);
+ if (!context) {
+ throw new Error(
+ "useFiltersContext must be used within a FiltersProvider"
+ );
+ }
+ return context;
+};
+
+export const ReviewsProvider = ({children, productId}: ReviewsProviderProps) => {
+ const authContext = useAuth();
+ const pageSize = 6;
+ const [isAbleToAdd, setIsAbleToAdd] = useState(true);
+ const [page, setPage] = useState(0);
+ const [rating, setRating] = useState(0);
+ const [reviews, setReviews] = useState(undefined);
+ const [totalReviews, setTotalReviews] = useState(0);
+ const [refresh, setRefresh] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ useEffect(() => {
+ setIsLoading(true);
+ loadReviews();
+ }, [refresh, page]);
+
+ const refreshReviews = () => {
+ setRefresh(!refresh);
+ }
+
+ const loadReviews = () => {
+ axiosInstance.get(`/review/ProductReview/${productId}?page=${page + 1}&pageSize=${pageSize}&clientId=${authContext.user?.userId ?? ""}`)
+ .then(response => response.data)
+ .then(data => {
+ setRating(data.data.averageRating);
+ setTotalReviews(data.data.reviews.totalItems);
+ setReviews(data.data);
+ setIsAbleToAdd(data.data.clientAbleToAdd);
+ setTimeout(() => setIsLoading(false), 300);
+ })
+ .catch(e => {
+ if (e.response.status != 404) {
+ toast.error(e.message);
+ }
+ })
+ }
+
+ const objValue = useMemo(() => ({
+ isAbleToAdd,
+ isLoading,
+ productId,
+ rating,
+ totalReviews,
+ reviews,
+ page,
+ setRating,
+ refreshReviews,
+ setPage,
+ setIsLoading,
+ setIsAbleToAdd
+ }), [productId, rating, reviews, totalReviews, isLoading, page, isAbleToAdd]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/merchant-web/src/models/Reviews.ts b/apps/merchant-web/src/models/Reviews.ts
new file mode 100644
index 00000000..070844ae
--- /dev/null
+++ b/apps/merchant-web/src/models/Reviews.ts
@@ -0,0 +1,20 @@
+
+interface Reviews {
+ productId: string;
+ averageRating: number;
+ reviews: {
+ items: Array
+ totalCount: number;
+ page: number;
+ pageSize: number;
+ totalItems: number;
+ totalPages: number;
+ }
+}
+
+interface Review {
+ clientName: string;
+ rating: number;
+ comment: string;
+ createdAt: string;
+}
diff --git a/apps/merchant-web/src/schemes/reviews/ReviewFormSchema.ts b/apps/merchant-web/src/schemes/reviews/ReviewFormSchema.ts
new file mode 100644
index 00000000..d3bb2707
--- /dev/null
+++ b/apps/merchant-web/src/schemes/reviews/ReviewFormSchema.ts
@@ -0,0 +1,12 @@
+import z from "zod"
+
+export const ReviewFormSchema = z.object({
+ comment: z.string()
+ .max(500, "Your opinion could only contain 500 characters")
+});
+
+export type ReviewFormData = z.infer;
+
+export const defaultReviewFormData: ReviewFormData = {
+ comment: ""
+}
diff --git a/apps/merchant-web/src/styles/reviews/ReviewSection.module.css b/apps/merchant-web/src/styles/reviews/ReviewSection.module.css
new file mode 100644
index 00000000..67adeb8d
--- /dev/null
+++ b/apps/merchant-web/src/styles/reviews/ReviewSection.module.css
@@ -0,0 +1,49 @@
+
+.gridContainer {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ justify-items: center;
+ gap: 40px;
+ width: 100%;
+}
+
+.addButton {
+ font-size: 14px;
+ font-weight: 300;
+ border-radius: 8px;
+}
+
+.addButton:hover {
+ text-decoration: underline;
+ color: var(--primary-400);
+ cursor: pointer;
+}
+
+.titleContainer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+}
+
+@media (max-width: 820px) {
+ .gridContainer {
+ grid-template-columns: 1fr;
+ }
+}
+
+.container {
+ padding: 0 24px;
+ margin-top: 40px;
+ margin-bottom: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.noReviewsMessage {
+ text-align: center;
+ margin: 24px;
+ padding-bottom: 24px;
+}
diff --git a/apps/merchant-web/src/styles/reviews/ReviewsCard.module.css b/apps/merchant-web/src/styles/reviews/ReviewsCard.module.css
new file mode 100644
index 00000000..7b2b6ed7
--- /dev/null
+++ b/apps/merchant-web/src/styles/reviews/ReviewsCard.module.css
@@ -0,0 +1,41 @@
+
+.container {
+ border: 1px solid var(--foreground-secondary);
+ width: 45vw;
+ min-width: 320px;
+ min-height: 150px;
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: 12px;
+}
+
+.skeleton {
+ background-color: var(--gray-200);
+ width: 45vw;
+ height: 150px;
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: 10px;
+}
+
+.roundedElement {
+ border-radius: 8px;
+}
+
+.name {
+ font-size: 24px;
+ font-weight: 400;
+}
+
+.dateStyle {
+ margin-top: 8px;
+ font-size: 14px;
+}
+
+.paragraph {
+ font-size: 14px;
+}
diff --git a/apps/merchant-web/src/utils/DateFormatter.ts b/apps/merchant-web/src/utils/DateFormatter.ts
new file mode 100644
index 00000000..dbd43e1a
--- /dev/null
+++ b/apps/merchant-web/src/utils/DateFormatter.ts
@@ -0,0 +1,8 @@
+export const FormatDateLiterary = (isoDate) => {
+ const date = new Date(isoDate);
+ return new Intl.DateTimeFormat("en-US", {
+ month: "long",
+ day: "numeric",
+ year: "numeric"
+ }).format(date);
+}
diff --git a/apps/merchant-web/src/utils/ZodFunctions.ts b/apps/merchant-web/src/utils/ZodFunctions.ts
new file mode 100644
index 00000000..289ba099
--- /dev/null
+++ b/apps/merchant-web/src/utils/ZodFunctions.ts
@@ -0,0 +1,11 @@
+import {z} from "zod";
+
+export const validateWithZod = (schema: z.ZodSchema) => (values: any) => {
+ try {
+ schema.parse(values);
+ return {};
+ } catch (error) {
+ const zodErrors = (error as z.ZodError).flatten();
+ return zodErrors.fieldErrors;
+ }
+};