Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
});
}

Expand All @@ -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;
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 =>
Expand All @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Commons.Messages;
using InventoryService.Intraestructure.Repositories.Interfaces;
using MassTransit;

namespace InventoryService.Api.QueueHandlers;

public class RatingUpdateHandler(IProductRepository repository) : IConsumer<RatingMessage>
{
public async Task Consume(ConsumeContext<RatingMessage> context)
{
var ratingUpdated = context.Message;
await repository.UpdateRating(ratingUpdated.ProductId, ratingUpdated.Rating);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProductCategory> Categories { get; set; } = [];
public List<ProductVariantImageDto> Images { get; set; } = [];
public List<ProductVariantDto> ProductVariants { get; set; } = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public ProductDto GetProductDtoByProduct(Product existingProduct, List<Guid> 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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Image> Images { get; set; } = new List<Image>();
public ICollection<ProductVariant> ProductVariants { get; set; } = new List<ProductVariant>();
public ICollection<Category> Categories { get; set; } = new List<Category>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,14 @@ public async Task<int> GetCountProductsByStoreId(Guid id, ProductFilteringQueryP
}
return await query.CountAsync();
}

public async Task<bool> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public interface IProductRepository : IRepository<Product>
{
Task<IEnumerable<Product>> GetProductsByStoreId(Guid id, int pageNumber, int pageSize, ProductFilteringQueryParams queryParams);
Task<int> GetCountProductsByStoreId(Guid id, ProductFilteringQueryParams queryParams);
Task<bool> UpdateRating(Guid id, float rating);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ private async Task<List<Expression<Func<Product, bool>>>> _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;
Expand Down Expand Up @@ -91,7 +91,14 @@ public IQueryable<Product> _ApplySort(IQueryable<Product> query, ProductFilterin

if (queryParams.RatingAsc.HasValue)
{
// TODO: Add support for rating sort.
query = (bool)queryParams.RatingAsc
? someSortApplied
? ((IOrderedQueryable<Product>)query).ThenBy(p => p.Rating)
: query.OrderBy(p => p.Rating)
: someSortApplied
? ((IOrderedQueryable<Product>)query).ThenByDescending(p => p.Rating)
: query.OrderByDescending(p => p.Rating);
someSortApplied = true;
}

if (!someSortApplied)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Commons.Dto;

public class PaginatedResponseDto<T>(List<T> items, int totalItems, int page, int pageSize)
{
public List<T>? 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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Commons.Messages;

public class RatingMessage
{
public required Guid ProductId { get; set; }
public required float Rating { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
29 changes: 29 additions & 0 deletions apps/merchant-api/ReviewService/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
@@ -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<IMongoDatabase>(_ => db);
services.ConfigContext();
return services;
}

public static IServiceCollection ConfigContext(this IServiceCollection services)
{
services.AddScoped<IContext<ProductReview>, ProductReviewContext>();
return services;
}
}
Original file line number Diff line number Diff line change
@@ -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<IRepository<ProductReview>, ProductReviewRepository>();
}
}
Original file line number Diff line number Diff line change
@@ -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<ActionResult> Create(CreateProductReviewDto productReview)
{
var result = await mediator.Send(new CreateProductReview(productReview));
if (result is ErrorResponse errorResponse)
return StatusCode(errorResponse.StatusCode, errorResponse);
var successResponse = (SuccessResponse<ProductReview>)result;
return StatusCode(successResponse.StatusCode, successResponse);
}

[HttpPost("{productId}")]
public async Task<ActionResult> 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<Review>)result;
return StatusCode(successResponse.StatusCode, successResponse);
}

[HttpGet("{id}")]
public async Task<ActionResult> 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<ProductReviewDto>)result;
return StatusCode(successResponse.StatusCode, successResponse);
}

[HttpPatch("{productId}")]
public async Task<ActionResult> 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<Review>)result;
return StatusCode(successResponse.StatusCode, successResponse);
}

[HttpDelete("{productId}")]
public async Task<ActionResult> 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<Review>)result;
return StatusCode(successResponse.StatusCode, successResponse);
}
}
Loading