diff --git a/src/FrontOffice.BFF.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesResponseDto.cs b/src/FrontOffice.BFF.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesResponseDto.cs index f28fe2f..f8ba6f0 100644 --- a/src/FrontOffice.BFF.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesResponseDto.cs +++ b/src/FrontOffice.BFF.Application/CommissionCQ/Queries/GetMyWeeklyBalances/GetMyWeeklyBalancesResponseDto.cs @@ -79,4 +79,9 @@ public class WeeklyBalanceItemDto /// آیا منقضی شده؟ /// public bool IsExpired { get; set; } + + /// + /// تاریخ شمسی + /// + public string DatePersian { get; set; } = string.Empty; } diff --git a/src/FrontOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs b/src/FrontOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs index 89c0c14..45fea88 100644 --- a/src/FrontOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs +++ b/src/FrontOffice.BFF.Application/Common/Interfaces/IApplicationContractContext.cs @@ -16,6 +16,10 @@ using CMSMicroservice.Protobuf.Protos.UserWallet; using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; using CMSMicroservice.Protobuf.Protos.ClubMembership; using CMSMicroservice.Protobuf.Protos.NetworkMembership; +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; +using CMSMicroservice.Protobuf.Protos.DiscountOrder; using PYMSMicroservice.Protobuf.Protos.Transaction; namespace FrontOffice.BFF.Application.Common.Interfaces; @@ -49,6 +53,12 @@ public interface IApplicationContractContext // Network & Club System ClubMembershipContract.ClubMembershipContractClient ClubMemberships { get; } NetworkMembershipContract.NetworkMembershipContractClient NetworkMemberships { get; } + + // Discount Shop System + DiscountProductContract.DiscountProductContractClient DiscountProducts { get; } + DiscountCategoryContract.DiscountCategoryContractClient DiscountCategories { get; } + DiscountShoppingCartContract.DiscountShoppingCartContractClient DiscountCart { get; } + DiscountOrderContract.DiscountOrderContractClient DiscountOrders { get; } #endregion #region PYMS diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/AddToDiscountCart/AddToDiscountCartCommand.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/AddToDiscountCart/AddToDiscountCartCommand.cs new file mode 100644 index 0000000..bba6fc1 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/AddToDiscountCart/AddToDiscountCartCommand.cs @@ -0,0 +1,23 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.AddToDiscountCart; + +/// +/// افزودن محصول به سبد خرید تخفیفی +/// +public record AddToDiscountCartCommand : IRequest +{ + /// + /// شناسه محصول + /// + public long ProductId { get; init; } + + /// + /// تعداد + /// + public int Count { get; init; } = 1; +} + +public class AddToDiscountCartResponseDto +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/AddToDiscountCart/AddToDiscountCartCommandHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/AddToDiscountCart/AddToDiscountCartCommandHandler.cs new file mode 100644 index 0000000..65309fd --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/AddToDiscountCart/AddToDiscountCartCommandHandler.cs @@ -0,0 +1,33 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.AddToDiscountCart; + +public class AddToDiscountCartCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + private readonly ICurrentUserService _currentUserService; + + public AddToDiscountCartCommandHandler(IApplicationContractContext context, ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public async Task Handle(AddToDiscountCartCommand request, CancellationToken cancellationToken) + { + var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException("User not authenticated"); + + var response = await _context.DiscountCart.AddToCartAsync(new AddToCartRequest + { + UserId = userId, + ProductId = request.ProductId, + Count = request.Count + }, cancellationToken: cancellationToken); + + return new AddToDiscountCartResponseDto + { + Success = response.Success, + Message = response.Message + }; + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/PlaceDiscountOrder/PlaceDiscountOrderCommand.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/PlaceDiscountOrder/PlaceDiscountOrderCommand.cs new file mode 100644 index 0000000..a96458d --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/PlaceDiscountOrder/PlaceDiscountOrderCommand.cs @@ -0,0 +1,39 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.PlaceDiscountOrder; + +/// +/// ثبت سفارش از سبد خرید تخفیفی +/// +public record PlaceDiscountOrderCommand : IRequest +{ + /// + /// شناسه آدرس کاربر + /// + public long UserAddressId { get; init; } + + /// + /// مقدار استفاده از موجودی تخفیف (اختیاری) + /// + public long DiscountBalanceToUse { get; init; } + + /// + /// توضیحات + /// + public string? Notes { get; init; } +} + +public class PlaceDiscountOrderResponseDto +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; + public long OrderId { get; set; } + + /// + /// مبلغ قابل پرداخت از درگاه + /// + public long GatewayAmount { get; set; } + + /// + /// لینک پرداخت (اگر نیاز باشد) + /// + public string? PaymentUrl { get; set; } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/PlaceDiscountOrder/PlaceDiscountOrderCommandHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/PlaceDiscountOrder/PlaceDiscountOrderCommandHandler.cs new file mode 100644 index 0000000..eacdff0 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/PlaceDiscountOrder/PlaceDiscountOrderCommandHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Protobuf.Protos.DiscountOrder; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.PlaceDiscountOrder; + +public class PlaceDiscountOrderCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + private readonly ICurrentUserService _currentUserService; + + public PlaceDiscountOrderCommandHandler(IApplicationContractContext context, ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public async Task Handle(PlaceDiscountOrderCommand request, CancellationToken cancellationToken) + { + var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException("User not authenticated"); + + var cmsRequest = new PlaceOrderRequest + { + UserId = userId, + UserAddressId = request.UserAddressId, + DiscountBalanceToUse = request.DiscountBalanceToUse + }; + + if (!string.IsNullOrEmpty(request.Notes)) + cmsRequest.Notes = request.Notes; + + var response = await _context.DiscountOrders.PlaceOrderAsync(cmsRequest, cancellationToken: cancellationToken); + + return new PlaceDiscountOrderResponseDto + { + Success = response.Success, + Message = response.Message, + OrderId = response.OrderId, + GatewayAmount = response.GatewayAmount, + PaymentUrl = response.PaymentUrl + }; + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/RemoveFromDiscountCart/RemoveFromDiscountCartCommand.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/RemoveFromDiscountCart/RemoveFromDiscountCartCommand.cs new file mode 100644 index 0000000..e7db756 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/RemoveFromDiscountCart/RemoveFromDiscountCartCommand.cs @@ -0,0 +1,18 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.RemoveFromDiscountCart; + +/// +/// حذف محصول از سبد خرید تخفیفی +/// +public record RemoveFromDiscountCartCommand : IRequest +{ + /// + /// شناسه محصول + /// + public long ProductId { get; init; } +} + +public class RemoveFromDiscountCartResponseDto +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/RemoveFromDiscountCart/RemoveFromDiscountCartCommandHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/RemoveFromDiscountCart/RemoveFromDiscountCartCommandHandler.cs new file mode 100644 index 0000000..88cf3c5 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Commands/RemoveFromDiscountCart/RemoveFromDiscountCartCommandHandler.cs @@ -0,0 +1,32 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.RemoveFromDiscountCart; + +public class RemoveFromDiscountCartCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + private readonly ICurrentUserService _currentUserService; + + public RemoveFromDiscountCartCommandHandler(IApplicationContractContext context, ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public async Task Handle(RemoveFromDiscountCartCommand request, CancellationToken cancellationToken) + { + var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException("User not authenticated"); + + var response = await _context.DiscountCart.RemoveFromCartAsync(new RemoveFromCartRequest + { + UserId = userId, + ProductId = request.ProductId + }, cancellationToken: cancellationToken); + + return new RemoveFromDiscountCartResponseDto + { + Success = response.Success, + Message = response.Message + }; + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs new file mode 100644 index 0000000..b0945c0 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQuery.cs @@ -0,0 +1,17 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories; + +/// +/// دریافت لیست دسته‌بندی‌های فروشگاه تخفیفی +/// +public record GetDiscountCategoriesQuery : IRequest +{ + /// + /// شناسه دسته‌بندی والد (اختیاری) + /// + public long? ParentCategoryId { get; init; } + + /// + /// فقط فعال‌ها + /// + public bool OnlyActive { get; init; } = true; +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs new file mode 100644 index 0000000..802b927 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesQueryHandler.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using CMSProto = CMSMicroservice.Protobuf.Protos.DiscountCategory; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories; + +public class GetDiscountCategoriesQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetDiscountCategoriesQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetDiscountCategoriesQuery request, CancellationToken cancellationToken) + { + var cmsRequest = new GetDiscountCategoriesRequest(); + + if (request.ParentCategoryId.HasValue) + cmsRequest.ParentCategoryId = request.ParentCategoryId.Value; + + if (request.OnlyActive) + cmsRequest.IsActive = true; + + var response = await _context.DiscountCategories.GetDiscountCategoriesAsync(cmsRequest, cancellationToken: cancellationToken); + + // Proto already returns tree structure with children + var categories = response.Categories.Select(MapToDto).ToList(); + + return new GetDiscountCategoriesResponseDto + { + Categories = categories + }; + } + + private static DiscountCategoryDto MapToDto(CMSProto.DiscountCategoryDto model) + { + return new DiscountCategoryDto + { + Id = model.Id, + Name = model.Name, + Title = model.Title, + Description = model.Description, + ImagePath = model.ImagePath, + ParentCategoryId = model.ParentCategoryId > 0 ? model.ParentCategoryId : null, + SortOrder = model.SortOrder, + IsActive = model.IsActive, + Children = model.Children.Select(MapToDto).ToList() + }; + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs new file mode 100644 index 0000000..72e9c1c --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountCategories/GetDiscountCategoriesResponseDto.cs @@ -0,0 +1,54 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories; + +public class GetDiscountCategoriesResponseDto +{ + public List Categories { get; set; } = new(); +} + +public class DiscountCategoryDto +{ + /// + /// شناسه + /// + public long Id { get; set; } + + /// + /// نام + /// + public string Name { get; set; } = string.Empty; + + /// + /// عنوان + /// + public string Title { get; set; } = string.Empty; + + /// + /// توضیحات + /// + public string? Description { get; set; } + + /// + /// مسیر تصویر + /// + public string? ImagePath { get; set; } + + /// + /// شناسه والد + /// + public long? ParentCategoryId { get; set; } + + /// + /// ترتیب نمایش + /// + public int SortOrder { get; set; } + + /// + /// فعال بودن + /// + public bool IsActive { get; set; } + + /// + /// زیرمجموعه‌ها + /// + public List Children { get; set; } = new(); +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs new file mode 100644 index 0000000..2ebf22a --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQuery.cs @@ -0,0 +1,27 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts; + +/// +/// دریافت لیست محصولات فروشگاه تخفیفی +/// +public record GetDiscountProductsQuery : IRequest +{ + /// + /// شناسه دسته‌بندی (اختیاری) + /// + public long? CategoryId { get; init; } + + /// + /// شماره صفحه + /// + public int PageNumber { get; init; } = 1; + + /// + /// تعداد در هر صفحه + /// + public int PageSize { get; init; } = 20; + + /// + /// عبارت جستجو (اختیاری) + /// + public string? SearchTerm { get; init; } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs new file mode 100644 index 0000000..759cc8c --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs @@ -0,0 +1,50 @@ +using CMSMicroservice.Protobuf.Protos.DiscountProduct; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts; + +public class GetDiscountProductsQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetDiscountProductsQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetDiscountProductsQuery request, CancellationToken cancellationToken) + { + var cmsRequest = new GetDiscountProductsRequest + { + PageNumber = request.PageNumber, + PageSize = request.PageSize + }; + + if (request.CategoryId.HasValue) + cmsRequest.CategoryId = request.CategoryId.Value; + + if (!string.IsNullOrEmpty(request.SearchTerm)) + cmsRequest.SearchQuery = request.SearchTerm; + + var response = await _context.DiscountProducts.GetDiscountProductsAsync(cmsRequest, cancellationToken: cancellationToken); + + return new GetDiscountProductsResponseDto + { + MetaData = new MetaData + { + TotalCount = response.MetaData?.TotalCount ?? 0 + }, + Products = response.Models.Select(p => new DiscountProductDto + { + Id = p.Id, + Title = p.Title, + ShortInformation = p.ShortInfomation, + Price = p.Price, + MaxDiscountPercent = p.MaxDiscountPercent, + ImagePath = p.ImagePath, + ThumbnailPath = p.ThumbnailPath, + AvailableCount = p.RemainingCount, + IsActive = p.IsActive + }).ToList() + }; + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs new file mode 100644 index 0000000..cf8c09a --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsResponseDto.cs @@ -0,0 +1,65 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts; + +public class GetDiscountProductsResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Products { get; set; } = new(); +} + +public class DiscountProductDto +{ + /// + /// شناسه محصول + /// + public long Id { get; set; } + + /// + /// عنوان + /// + public string Title { get; set; } = string.Empty; + + /// + /// توضیحات کوتاه + /// + public string ShortInformation { get; set; } = string.Empty; + + /// + /// قیمت اصلی + /// + public long Price { get; set; } + + /// + /// حداکثر درصد تخفیف + /// + public int MaxDiscountPercent { get; set; } + + /// + /// قیمت با تخفیف + /// + public long DiscountedPrice => Price - (Price * MaxDiscountPercent / 100); + + /// + /// مسیر تصویر + /// + public string ImagePath { get; set; } = string.Empty; + + /// + /// مسیر تصویر کوچک + /// + public string ThumbnailPath { get; set; } = string.Empty; + + /// + /// موجودی + /// + public int AvailableCount { get; set; } + + /// + /// فعال بودن + /// + public bool IsActive { get; set; } + + /// + /// دسته‌بندی‌ها + /// + public List CategoryIds { get; set; } = new(); +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartQuery.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartQuery.cs new file mode 100644 index 0000000..fa7eb1a --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartQuery.cs @@ -0,0 +1,6 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart; + +/// +/// دریافت سبد خرید فروشگاه تخفیفی کاربر جاری +/// +public record GetMyDiscountCartQuery : IRequest; diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartQueryHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartQueryHandler.cs new file mode 100644 index 0000000..7aa1f81 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartQueryHandler.cs @@ -0,0 +1,42 @@ +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart; + +public class GetMyDiscountCartQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + private readonly ICurrentUserService _currentUserService; + + public GetMyDiscountCartQueryHandler(IApplicationContractContext context, ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public async Task Handle(GetMyDiscountCartQuery request, CancellationToken cancellationToken) + { + var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException("User not authenticated"); + + var response = await _context.DiscountCart.GetUserCartAsync(new GetUserCartRequest + { + UserId = userId + }, cancellationToken: cancellationToken); + + return new GetMyDiscountCartResponseDto + { + Items = response.Items.Select(i => new DiscountCartItemDto + { + Id = i.ProductId, // Use ProductId as unique identifier + ProductId = i.ProductId, + ProductTitle = i.ProductTitle, + UnitPrice = i.UnitPrice, + Count = i.Count, + MaxDiscountPercent = i.MaxDiscountPercent, + DiscountAmount = i.DiscountAmount, + ThumbnailPath = i.ProductImagePath + }).ToList(), + TotalPrice = response.TotalPrice, + TotalDiscountAmount = response.TotalDiscountAmount + }; + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartResponseDto.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartResponseDto.cs new file mode 100644 index 0000000..67c94cd --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountCart/GetMyDiscountCartResponseDto.cs @@ -0,0 +1,77 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart; + +public class GetMyDiscountCartResponseDto +{ + /// + /// آیتم‌های سبد خرید + /// + public List Items { get; set; } = new(); + + /// + /// جمع کل قیمت (بدون تخفیف) + /// + public long TotalPrice { get; set; } + + /// + /// جمع کل تخفیف + /// + public long TotalDiscountAmount { get; set; } + + /// + /// مبلغ قابل پرداخت + /// + public long PayableAmount => TotalPrice - TotalDiscountAmount; + + /// + /// تعداد کل آیتم‌ها + /// + public int TotalItemCount => Items.Sum(i => i.Count); +} + +public class DiscountCartItemDto +{ + /// + /// شناسه آیتم سبد + /// + public long Id { get; set; } + + /// + /// شناسه محصول + /// + public long ProductId { get; set; } + + /// + /// عنوان محصول + /// + public string ProductTitle { get; set; } = string.Empty; + + /// + /// قیمت واحد + /// + public long UnitPrice { get; set; } + + /// + /// تعداد + /// + public int Count { get; set; } + + /// + /// درصد تخفیف + /// + public int MaxDiscountPercent { get; set; } + + /// + /// مبلغ تخفیف + /// + public long DiscountAmount { get; set; } + + /// + /// قیمت کل ردیف + /// + public long TotalPrice => UnitPrice * Count; + + /// + /// مسیر تصویر + /// + public string ThumbnailPath { get; set; } = string.Empty; +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersQuery.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersQuery.cs new file mode 100644 index 0000000..0b11b2d --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersQuery.cs @@ -0,0 +1,10 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders; + +/// +/// دریافت سفارشات فروشگاه تخفیفی کاربر +/// +public record GetMyDiscountOrdersQuery : IRequest +{ + public int PageNumber { get; init; } = 1; + public int PageSize { get; init; } = 20; +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersQueryHandler.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersQueryHandler.cs new file mode 100644 index 0000000..f0c589b --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersQueryHandler.cs @@ -0,0 +1,80 @@ +using CMSMicroservice.Protobuf.Protos.DiscountOrder; + +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders; + +public class GetMyDiscountOrdersQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + private readonly ICurrentUserService _currentUserService; + + public GetMyDiscountOrdersQueryHandler(IApplicationContractContext context, ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public async Task Handle(GetMyDiscountOrdersQuery request, CancellationToken cancellationToken) + { + var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException("User not authenticated"); + + var response = await _context.DiscountOrders.GetUserOrdersAsync(new GetUserOrdersRequest + { + UserId = userId, + PageNumber = request.PageNumber, + PageSize = request.PageSize + }, cancellationToken: cancellationToken); + + return new GetMyDiscountOrdersResponseDto + { + MetaData = new MetaData + { + TotalCount = response.MetaData?.TotalCount ?? 0 + }, + Orders = response.Models.Select(o => new DiscountOrderDto + { + Id = o.Id, + OrderNumber = o.OrderNumber, + Status = MapStatus((int)o.DeliveryStatus), + StatusColor = GetStatusColor((int)o.DeliveryStatus), + TotalAmount = o.TotalPrice, + DiscountUsed = o.DiscountBalanceUsed, + GatewayPaid = o.GatewayAmount, + CreatedAt = o.Created?.ToDateTime() ?? DateTime.UtcNow, + CreatedAtPersian = FormatPersianDate(o.Created?.ToDateTime()) + }).ToList() + }; + } + + private static string MapStatus(int status) + { + return status switch + { + 0 => "در انتظار پرداخت", + 1 => "در حال پردازش", + 2 => "ارسال شده", + 3 => "تحویل داده شده", + 4 => "لغو شده", + _ => "نامشخص" + }; + } + + private static string GetStatusColor(int status) + { + return status switch + { + 0 => "warning", + 1 => "primary", + 2 => "info", + 3 => "success", + 4 => "danger", + _ => "secondary" + }; + } + + private static string FormatPersianDate(DateTime? date) + { + if (!date.HasValue) return string.Empty; + // TODO: استفاده از PersianCalendar + return date.Value.ToString("yyyy/MM/dd HH:mm"); + } +} diff --git a/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersResponseDto.cs b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersResponseDto.cs new file mode 100644 index 0000000..56c3ee1 --- /dev/null +++ b/src/FrontOffice.BFF.Application/DiscountShopCQ/Queries/GetMyDiscountOrders/GetMyDiscountOrdersResponseDto.cs @@ -0,0 +1,55 @@ +namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders; + +public class GetMyDiscountOrdersResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Orders { get; set; } = new(); +} + +public class DiscountOrderDto +{ + /// + /// شناسه سفارش + /// + public long Id { get; set; } + + /// + /// شماره سفارش + /// + public string OrderNumber { get; set; } = string.Empty; + + /// + /// وضعیت + /// + public string Status { get; set; } = string.Empty; + + /// + /// رنگ وضعیت + /// + public string StatusColor { get; set; } = string.Empty; + + /// + /// جمع کل + /// + public long TotalAmount { get; set; } + + /// + /// تخفیف استفاده شده + /// + public long DiscountUsed { get; set; } + + /// + /// مبلغ پرداخت شده از درگاه + /// + public long GatewayPaid { get; set; } + + /// + /// تاریخ ثبت + /// + public DateTime CreatedAt { get; set; } + + /// + /// تاریخ شمسی + /// + public string CreatedAtPersian { get; set; } = string.Empty; +} diff --git a/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionQuery.cs b/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionQuery.cs new file mode 100644 index 0000000..c1b55ca --- /dev/null +++ b/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionQuery.cs @@ -0,0 +1,3 @@ +namespace FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition; + +public record GetMyNetworkPositionQuery : IRequest; diff --git a/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionQueryHandler.cs b/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionQueryHandler.cs new file mode 100644 index 0000000..8c1e6c0 --- /dev/null +++ b/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionQueryHandler.cs @@ -0,0 +1,44 @@ +using CMSMicroservice.Protobuf.Protos.NetworkMembership; + +namespace FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition; + +public class GetMyNetworkPositionQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + private readonly ICurrentUserService _currentUserService; + + public GetMyNetworkPositionQueryHandler( + IApplicationContractContext context, + ICurrentUserService currentUserService) + { + _context = context; + _currentUserService = currentUserService; + } + + public async Task Handle(GetMyNetworkPositionQuery request, CancellationToken cancellationToken) + { + var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException("User not authenticated"); + + var response = await _context.NetworkMemberships.GetUserNetworkAsync( + new GetUserNetworkRequest { UserId = userId }, + cancellationToken: cancellationToken); + + return new GetMyNetworkPositionResponseDto + { + UserId = response.UserId, + ParentId = response.ParentId ?? 0, + ParentName = response.ParentName ?? string.Empty, + Position = response.NetworkLeg switch + { + 0 => "Left", + 1 => "Right", + _ => "Root" + }, + Level = response.NetworkLevel, + ReferralCode = response.ReferralCode ?? string.Empty, + JoinedAt = response.JoinedAt?.ToDateTime() ?? DateTime.UtcNow, + HasLeftChild = response.LeftChildId.HasValue, + HasRightChild = response.RightChildId.HasValue + }; + } +} diff --git a/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionResponseDto.cs b/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionResponseDto.cs new file mode 100644 index 0000000..5482c73 --- /dev/null +++ b/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkPosition/GetMyNetworkPositionResponseDto.cs @@ -0,0 +1,49 @@ +namespace FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition; + +public class GetMyNetworkPositionResponseDto +{ + /// + /// شناسه کاربر + /// + public long UserId { get; set; } + + /// + /// شناسه والد + /// + public long ParentId { get; set; } + + /// + /// نام والد + /// + public string ParentName { get; set; } = string.Empty; + + /// + /// موقعیت در درخت (Left/Right) + /// + public string Position { get; set; } = string.Empty; + + /// + /// سطح در درخت + /// + public int Level { get; set; } + + /// + /// کد معرف + /// + public string ReferralCode { get; set; } = string.Empty; + + /// + /// تاریخ عضویت + /// + public DateTime JoinedAt { get; set; } + + /// + /// آیا فرزند چپ دارد؟ + /// + public bool HasLeftChild { get; set; } + + /// + /// آیا فرزند راست دارد؟ + /// + public bool HasRightChild { get; set; } +} diff --git a/src/FrontOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs b/src/FrontOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs index e56686f..3bd2566 100644 --- a/src/FrontOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs +++ b/src/FrontOffice.BFF.Infrastructure/Services/ApplicationContractContext.cs @@ -16,6 +16,10 @@ using CMSMicroservice.Protobuf.Protos.UserWallet; using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; using CMSMicroservice.Protobuf.Protos.ClubMembership; using CMSMicroservice.Protobuf.Protos.NetworkMembership; +using CMSMicroservice.Protobuf.Protos.DiscountProduct; +using CMSMicroservice.Protobuf.Protos.DiscountCategory; +using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; +using CMSMicroservice.Protobuf.Protos.DiscountOrder; using FrontOffice.BFF.Application.Common.Interfaces; using Microsoft.Extensions.DependencyInjection; using PYMSMicroservice.Protobuf.Protos.Transaction; @@ -76,6 +80,12 @@ public class ApplicationContractContext : IApplicationContractContext // Network & Club System public ClubMembershipContract.ClubMembershipContractClient ClubMemberships => GetService(); public NetworkMembershipContract.NetworkMembershipContractClient NetworkMemberships => GetService(); + + // Discount Shop System + public DiscountProductContract.DiscountProductContractClient DiscountProducts => GetService(); + public DiscountCategoryContract.DiscountCategoryContractClient DiscountCategories => GetService(); + public DiscountShoppingCartContract.DiscountShoppingCartContractClient DiscountCart => GetService(); + public DiscountOrderContract.DiscountOrderContractClient DiscountOrders => GetService(); #endregion #region PYMS diff --git a/src/FrontOffice.BFF.WebApi/Common/Mappings/ClubMembershipProfile.cs b/src/FrontOffice.BFF.WebApi/Common/Mappings/ClubMembershipProfile.cs new file mode 100644 index 0000000..f8f24dd --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Common/Mappings/ClubMembershipProfile.cs @@ -0,0 +1,42 @@ +using FrontOffice.BFF.Application.ClubMembershipCQ.Queries.GetMyClubMembership; +using FrontOffice.BFF.Application.ClubMembershipCQ.Commands.ActivateMyClubMembership; +using Google.Protobuf.WellKnownTypes; +using ProtoDto = FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; + +namespace FrontOffice.BFF.WebApi.Common.Mappings; + +public class ClubMembershipProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // Request -> Command mappings + config.NewConfig() + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.ActivationCode, src => src.ActivationCode) + .Map(dest => dest.DurationMonths, src => src.DurationMonths > 0 ? src.DurationMonths : 12); + + // Response mappings + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.PackageId, src => src.PackageId) + .Map(dest => dest.PackageName, src => src.PackageName) + .Map(dest => dest.ActivationCode, src => src.ActivationCode) + .Map(dest => dest.IsActive, src => src.IsActive) + .Map(dest => dest.ActivationDate, src => src.ActivationDate.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.ActivationDate.Value, DateTimeKind.Utc)) + : null) + .Map(dest => dest.ExpirationDate, src => src.ExpirationDate.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.ExpirationDate.Value, DateTimeKind.Utc)) + : null) + .Map(dest => dest.Status, src => src.Status) + .Map(dest => dest.DaysRemaining, src => src.DaysRemaining) + .Map(dest => dest.IsTrialPeriod, src => src.IsTrialPeriod); + + config.NewConfig() + .Map(dest => dest.Success, src => src.Success) + .Map(dest => dest.Message, src => src.Message) + .Map(dest => dest.ActivationDate, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.ActivationDate, DateTimeKind.Utc))) + .Map(dest => dest.ExpirationDate, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.ExpirationDate, DateTimeKind.Utc))) + .Map(dest => dest.AmountPaid, src => src.AmountPaid); + } +} diff --git a/src/FrontOffice.BFF.WebApi/Common/Mappings/CommissionProfile.cs b/src/FrontOffice.BFF.WebApi/Common/Mappings/CommissionProfile.cs new file mode 100644 index 0000000..46cd8c1 --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Common/Mappings/CommissionProfile.cs @@ -0,0 +1,59 @@ +using FrontOffice.BFF.Application.CommissionCQ.Queries.GetMyCommissionPayouts; +using FrontOffice.BFF.Application.CommissionCQ.Queries.GetMyWeeklyBalances; +using Google.Protobuf.WellKnownTypes; +using ProtoDto = FrontOffice.BFF.Commission.Protobuf.Protos.Commission; + +namespace FrontOffice.BFF.WebApi.Common.Mappings; + +public class CommissionProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // Request -> Query mappings + config.NewConfig() + .Map(dest => dest.PageNumber, src => src.PageNumber) + .Map(dest => dest.PageSize, src => src.PageSize) + .Map(dest => dest.WeekNumber, src => src.WeekNumber) + .Map(dest => dest.Status, src => src.Status); + + config.NewConfig() + .Map(dest => dest.PageNumber, src => src.PageNumber) + .Map(dest => dest.PageSize, src => src.PageSize) + .Map(dest => dest.WeekNumber, src => src.WeekNumber) + .Map(dest => dest.OnlyActive, src => src.OnlyActive); + + // Response mappings + config.NewConfig() + .Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.TotalCount }) + .Map(dest => dest.Payouts, src => src.Payouts); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.WeekNumber, src => src.WeekNumber) + .Map(dest => dest.WeekLabel, src => src.WeekLabel) + .Map(dest => dest.BalancesEarned, src => src.BalancesEarned) + .Map(dest => dest.TotalAmount, src => src.TotalAmount) + .Map(dest => dest.AmountFormatted, src => src.AmountFormatted) + .Map(dest => dest.Status, src => src.Status) + .Map(dest => dest.StatusBadgeColor, src => src.StatusBadgeColor) + .Map(dest => dest.CalculatedDate, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedDate, DateTimeKind.Utc))) + .Map(dest => dest.DatePersian, src => src.DatePersian); + + config.NewConfig() + .Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.TotalCount }) + .Map(dest => dest.Balances, src => src.Balances) + .Map(dest => dest.TotalLeftBalances, src => src.TotalLeftBalances) + .Map(dest => dest.TotalRightBalances, src => src.TotalRightBalances) + .Map(dest => dest.WeakerLeg, src => src.WeakerLeg); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.WeekNumber, src => src.WeekNumber) + .Map(dest => dest.LeftLegBalances, src => src.LeftLegBalances) + .Map(dest => dest.RightLegBalances, src => src.RightLegBalances) + .Map(dest => dest.TotalBalances, src => src.TotalBalances) + .Map(dest => dest.WeeklyPoolContribution, src => src.WeeklyPoolContribution) + .Map(dest => dest.IsExpired, src => src.IsExpired) + .Map(dest => dest.DatePersian, src => src.DatePersian); + } +} diff --git a/src/FrontOffice.BFF.WebApi/Common/Mappings/DiscountShopProfile.cs b/src/FrontOffice.BFF.WebApi/Common/Mappings/DiscountShopProfile.cs new file mode 100644 index 0000000..f94ff14 --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Common/Mappings/DiscountShopProfile.cs @@ -0,0 +1,104 @@ +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders; +using FrontOffice.BFF.Application.DiscountShopCQ.Commands.AddToDiscountCart; +using FrontOffice.BFF.Application.DiscountShopCQ.Commands.RemoveFromDiscountCart; +using FrontOffice.BFF.Application.DiscountShopCQ.Commands.PlaceDiscountOrder; +using ProtoDto = FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop; + +namespace FrontOffice.BFF.WebApi.Common.Mappings; + +public class DiscountShopProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // Request -> Query/Command mappings + config.NewConfig() + .Map(dest => dest.PageNumber, src => src.PageNumber) + .Map(dest => dest.PageSize, src => src.PageSize) + .Map(dest => dest.SearchTerm, src => src.SearchTerm) + .Map(dest => dest.CategoryId, src => src.CategoryId); + + config.NewConfig() + .Map(dest => dest.ParentCategoryId, src => src.ParentCategoryId) + .Map(dest => dest.OnlyActive, src => src.OnlyActive); + + config.NewConfig() + .Map(dest => dest.PageNumber, src => src.PageNumber) + .Map(dest => dest.PageSize, src => src.PageSize); + + config.NewConfig() + .Map(dest => dest.ProductId, src => src.ProductId) + .Map(dest => dest.Count, src => src.Count); + + config.NewConfig() + .Map(dest => dest.ProductId, src => src.ProductId); + + config.NewConfig() + .Map(dest => dest.UserAddressId, src => src.UserAddressId) + .Map(dest => dest.DiscountBalanceToUse, src => src.DiscountBalanceToUse) + .Map(dest => dest.Notes, src => src.Notes); + + // Response mappings + config.NewConfig() + .Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.MetaData.TotalCount }) + .Map(dest => dest.Products, src => src.Products); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.Title, src => src.Title) + .Map(dest => dest.Description, src => src.ShortInformation) + .Map(dest => dest.ImagePath, src => src.ImagePath) + .Map(dest => dest.OriginalPrice, src => src.Price) + .Map(dest => dest.DiscountedPrice, src => src.DiscountedPrice) + .Map(dest => dest.DiscountPercent, src => src.MaxDiscountPercent) + .Map(dest => dest.RemainingCount, src => src.AvailableCount) + .Map(dest => dest.IsAvailable, src => src.IsActive); + + config.NewConfig() + .Map(dest => dest.Categories, src => src.Categories); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.Title, src => src.Title) + .Map(dest => dest.ParentId, src => src.ParentCategoryId) + .Map(dest => dest.IconPath, src => src.ImagePath) + .Map(dest => dest.SortOrder, src => src.SortOrder) + .Map(dest => dest.Children, src => src.Children); + + config.NewConfig() + .Map(dest => dest.Items, src => src.Items) + .Map(dest => dest.TotalPrice, src => src.TotalPrice) + .Map(dest => dest.TotalDiscountAmount, src => src.TotalDiscountAmount); + + config.NewConfig() + .Map(dest => dest.ProductId, src => src.ProductId) + .Map(dest => dest.ProductTitle, src => src.ProductTitle) + .Map(dest => dest.ProductImagePath, src => src.ThumbnailPath) + .Map(dest => dest.UnitPrice, src => src.UnitPrice) + .Map(dest => dest.Count, src => src.Count) + .Map(dest => dest.TotalPrice, src => src.TotalPrice); + + config.NewConfig() + .Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.MetaData.TotalCount }) + .Map(dest => dest.Orders, src => src.Orders); + + config.NewConfig() + .Map(dest => dest.Id, src => src.Id) + .Map(dest => dest.OrderNumber, src => src.OrderNumber) + .Map(dest => dest.Status, src => src.Status) + .Map(dest => dest.StatusColor, src => src.StatusColor) + .Map(dest => dest.TotalAmount, src => src.TotalAmount) + .Map(dest => dest.DiscountUsed, src => src.DiscountUsed) + .Map(dest => dest.GatewayPaid, src => src.GatewayPaid) + .Map(dest => dest.CreatedAtPersian, src => src.CreatedAtPersian); + + config.NewConfig() + .Map(dest => dest.Success, src => src.Success) + .Map(dest => dest.Message, src => src.Message) + .Map(dest => dest.OrderId, src => src.OrderId) + .Map(dest => dest.OrderNumber, src => src.OrderId.ToString()) + .Map(dest => dest.PaymentUrl, src => src.PaymentUrl); + } +} diff --git a/src/FrontOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs b/src/FrontOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs new file mode 100644 index 0000000..eb98596 --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs @@ -0,0 +1,68 @@ +using FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree; +using FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkStatistics; +using FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition; +using Google.Protobuf.WellKnownTypes; +using ProtoDto = FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; + +namespace FrontOffice.BFF.WebApi.Common.Mappings; + +public class NetworkMembershipProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // Request -> Query mappings + config.NewConfig() + .Map(dest => dest.MaxDepth, src => src.MaxDepth > 0 ? src.MaxDepth : 3); + + // Response mappings - Tree + config.NewConfig() + .Map(dest => dest.RootNode, src => src.RootNode) + .Map(dest => dest.TotalMembers, src => src.TotalMembers) + .Map(dest => dest.CurrentDepth, src => src.CurrentDepth); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.FullName, src => src.FullName) + .Map(dest => dest.Mobile, src => src.Mobile) + .Map(dest => dest.Avatar, src => src.Avatar) + .Map(dest => dest.Position, src => src.Position) + .Map(dest => dest.LeftChild, src => src.LeftChild) + .Map(dest => dest.RightChild, src => src.RightChild) + .Map(dest => dest.Level, src => src.Level) + .Map(dest => dest.HasChildren, src => src.HasChildren); + + // Response mappings - Statistics + config.NewConfig() + .Map(dest => dest.TotalMembers, src => src.TotalMembers) + .Map(dest => dest.ActiveMembers, src => src.ActiveMembers) + .Map(dest => dest.LeftLegCount, src => src.LeftLegCount) + .Map(dest => dest.RightLegCount, src => src.RightLegCount) + .Map(dest => dest.LeftPercentage, src => src.LeftPercentage) + .Map(dest => dest.RightPercentage, src => src.RightPercentage) + .Map(dest => dest.AverageDepth, src => src.AverageDepth) + .Map(dest => dest.MaxDepth, src => src.MaxDepth) + .Map(dest => dest.WeakerLeg, src => src.WeakerLeg) + .Map(dest => dest.MyNetworkLevel, src => src.MyNetworkLevel) + .Map(dest => dest.MyNetworkLeg, src => src.MyNetworkLeg) + .Map(dest => dest.MyReferralCode, src => src.MyReferralCode) + .Map(dest => dest.LastMember, src => src.LastMember); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.FullName, src => src.FullName) + .Map(dest => dest.Position, src => src.Position) + .Map(dest => dest.TotalChildren, src => src.TotalChildren); + + // Response mappings - Position + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.ParentId, src => src.ParentId) + .Map(dest => dest.ParentName, src => src.ParentName) + .Map(dest => dest.Position, src => src.Position) + .Map(dest => dest.Level, src => src.Level) + .Map(dest => dest.ReferralCode, src => src.ReferralCode) + .Map(dest => dest.JoinedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.JoinedAt, DateTimeKind.Utc))) + .Map(dest => dest.HasLeftChild, src => src.HasLeftChild) + .Map(dest => dest.HasRightChild, src => src.HasRightChild); + } +} diff --git a/src/FrontOffice.BFF.WebApi/FrontOffice.BFF.WebApi.csproj b/src/FrontOffice.BFF.WebApi/FrontOffice.BFF.WebApi.csproj index 4b15dc2..d117c8c 100644 --- a/src/FrontOffice.BFF.WebApi/FrontOffice.BFF.WebApi.csproj +++ b/src/FrontOffice.BFF.WebApi/FrontOffice.BFF.WebApi.csproj @@ -31,5 +31,9 @@ + + + + diff --git a/src/FrontOffice.BFF.WebApi/Services/ClubMembershipGrpcService.cs b/src/FrontOffice.BFF.WebApi/Services/ClubMembershipGrpcService.cs new file mode 100644 index 0000000..9457ade --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Services/ClubMembershipGrpcService.cs @@ -0,0 +1,35 @@ +using FrontOffice.BFF.WebApi.Common.Services; +using FrontOffice.BFF.Application.ClubMembershipCQ.Queries.GetMyClubMembership; +using FrontOffice.BFF.Application.ClubMembershipCQ.Commands.ActivateMyClubMembership; +using FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership; + +namespace FrontOffice.BFF.WebApi.Services; + +/// +/// سرویس عضویت باشگاه - برای کاربران FrontOffice +/// +public class ClubMembershipGrpcService : ClubMembershipContract.ClubMembershipContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public ClubMembershipGrpcService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + /// + /// دریافت وضعیت عضویت باشگاه کاربر جاری + /// + public override async Task GetMyClubMembership(Empty request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(context); + } + + /// + /// فعال‌سازی عضویت باشگاه (پرداخت 56M) + /// + public override async Task ActivateMyClubMembership(ActivateMyClubMembershipRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/FrontOffice.BFF.WebApi/Services/ClubMembershipService.cs b/src/FrontOffice.BFF.WebApi/Services/ClubMembershipService.cs index dd9bd39..bfa078d 100644 --- a/src/FrontOffice.BFF.WebApi/Services/ClubMembershipService.cs +++ b/src/FrontOffice.BFF.WebApi/Services/ClubMembershipService.cs @@ -1,6 +1,8 @@ using FrontOffice.BFF.WebApi.Common.Services; using FrontOffice.BFF.Application.ClubMembershipCQ.Queries.GetMyClubMembership; using FrontOffice.BFF.Application.ClubMembershipCQ.Commands.ActivateMyClubMembership; +using MediatR; +using System.Threading; namespace FrontOffice.BFF.WebApi.Services; @@ -11,19 +13,19 @@ namespace FrontOffice.BFF.WebApi.Services; /// public class ClubMembershipService { - private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; - public ClubMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public ClubMembershipService(ISender sender) { - _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; } /// /// دریافت وضعیت عضویت باشگاه کاربر جاری /// - public async Task GetMyClubMembership(Empty request, ServerCallContext context) + public async Task GetMyClubMembership(CancellationToken cancellationToken = default) { - return await _dispatchRequestToCQRS.Handle(context); + return await _sender.Send(new GetMyClubMembershipQuery(), cancellationToken); } /// @@ -31,8 +33,8 @@ public class ClubMembershipService /// public async Task ActivateMyClubMembership( ActivateMyClubMembershipCommand request, - ServerCallContext context) + CancellationToken cancellationToken = default) { - return await _dispatchRequestToCQRS.Handle(request, context); + return await _sender.Send(request, cancellationToken); } } diff --git a/src/FrontOffice.BFF.WebApi/Services/CommissionService.cs b/src/FrontOffice.BFF.WebApi/Services/CommissionService.cs new file mode 100644 index 0000000..4a19c5d --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Services/CommissionService.cs @@ -0,0 +1,35 @@ +using FrontOffice.BFF.WebApi.Common.Services; +using FrontOffice.BFF.Application.CommissionCQ.Queries.GetMyCommissionPayouts; +using FrontOffice.BFF.Application.CommissionCQ.Queries.GetMyWeeklyBalances; +using FrontOffice.BFF.Commission.Protobuf.Protos.Commission; + +namespace FrontOffice.BFF.WebApi.Services; + +/// +/// سرویس کمیسیون - برای کاربران FrontOffice +/// +public class CommissionService : CommissionContract.CommissionContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public CommissionService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + /// + /// دریافت لیست پرداخت‌های کمیسیون کاربر + /// + public override async Task GetMyCommissionPayouts(GetMyCommissionPayoutsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// دریافت لیست بالانس‌های هفتگی کاربر + /// + public override async Task GetMyWeeklyBalances(GetMyWeeklyBalancesRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/FrontOffice.BFF.WebApi/Services/DiscountShopService.cs b/src/FrontOffice.BFF.WebApi/Services/DiscountShopService.cs new file mode 100644 index 0000000..f6dfd98 --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Services/DiscountShopService.cs @@ -0,0 +1,80 @@ +using FrontOffice.BFF.WebApi.Common.Services; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart; +using FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders; +using FrontOffice.BFF.Application.DiscountShopCQ.Commands.AddToDiscountCart; +using FrontOffice.BFF.Application.DiscountShopCQ.Commands.RemoveFromDiscountCart; +using FrontOffice.BFF.Application.DiscountShopCQ.Commands.PlaceDiscountOrder; +using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop; + +namespace FrontOffice.BFF.WebApi.Services; + +/// +/// سرویس فروشگاه تخفیفی - برای کاربران عضو باشگاه +/// +public class DiscountShopService : DiscountShopContract.DiscountShopContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public DiscountShopService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + /// + /// دریافت لیست محصولات تخفیفی + /// + public override async Task GetDiscountProducts(GetDiscountProductsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// دریافت دسته‌بندی‌های فروشگاه تخفیفی + /// + public override async Task GetDiscountCategories(GetDiscountCategoriesRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// دریافت سبد خرید تخفیفی کاربر جاری + /// + public override async Task GetMyDiscountCart(Empty request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(context); + } + + /// + /// افزودن محصول به سبد خرید تخفیفی + /// + public override async Task AddToDiscountCart(AddToDiscountCartRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// حذف محصول از سبد خرید تخفیفی + /// + public override async Task RemoveFromDiscountCart(RemoveFromDiscountCartRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// دریافت لیست سفارشات تخفیفی کاربر جاری + /// + public override async Task GetMyDiscountOrders(GetMyDiscountOrdersRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// ثبت سفارش تخفیفی + /// + public override async Task PlaceDiscountOrder(PlaceDiscountOrderRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } +} diff --git a/src/FrontOffice.BFF.WebApi/Services/NetworkMembershipService.cs b/src/FrontOffice.BFF.WebApi/Services/NetworkMembershipService.cs new file mode 100644 index 0000000..4d025a8 --- /dev/null +++ b/src/FrontOffice.BFF.WebApi/Services/NetworkMembershipService.cs @@ -0,0 +1,44 @@ +using FrontOffice.BFF.WebApi.Common.Services; +using FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree; +using FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkStatistics; +using FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition; +using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership; + +namespace FrontOffice.BFF.WebApi.Services; + +/// +/// سرویس عضویت شبکه‌ای - برای کاربران FrontOffice +/// +public class NetworkMembershipService : NetworkMembershipContract.NetworkMembershipContractBase +{ + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public NetworkMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + /// + /// دریافت درخت شبکه کاربر جاری + /// + public override async Task GetMyNetworkTree(GetMyNetworkTreeRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + /// + /// دریافت آمار شبکه کاربر جاری + /// + public override async Task GetMyNetworkStatistics(Empty request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(context); + } + + /// + /// دریافت موقعیت کاربر در شبکه + /// + public override async Task GetMyNetworkPosition(Empty request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(context); + } +} diff --git a/src/FrontOffice.BFF.sln b/src/FrontOffice.BFF.sln index 493e8ed..6fdb68c 100644 --- a/src/FrontOffice.BFF.sln +++ b/src/FrontOffice.BFF.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -30,64 +31,228 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontOffice.BFF.ShopingCart EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontOffice.BFF.UserWallet.Protobuf", "Protobufs\FrontOffice.BFF.UserWallet.Protobuf\FrontOffice.BFF.UserWallet.Protobuf.csproj", "{03F99CE9-F952-47B0-B71A-1F4865E52443}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontOffice.BFF.DiscountShop.Protobuf", "Protobufs\FrontOffice.BFF.DiscountShop.Protobuf\FrontOffice.BFF.DiscountShop.Protobuf.csproj", "{5547FB9B-7AEF-49C7-AA11-119D37DD9528}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontOffice.BFF.Commission.Protobuf", "Protobufs\FrontOffice.BFF.Commission.Protobuf\FrontOffice.BFF.Commission.Protobuf.csproj", "{B1380466-18E7-4CAD-88F8-E1419D2B6300}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontOffice.BFF.ClubMembership.Protobuf", "Protobufs\FrontOffice.BFF.ClubMembership.Protobuf\FrontOffice.BFF.ClubMembership.Protobuf.csproj", "{B6EAE0A3-3427-4D86-B2BA-B185F476B74F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontOffice.BFF.NetworkMembership.Protobuf", "Protobufs\FrontOffice.BFF.NetworkMembership.Protobuf\FrontOffice.BFF.NetworkMembership.Protobuf.csproj", "{CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7E733B83-275C-4639-AA10-4A59B681B904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E733B83-275C-4639-AA10-4A59B681B904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Debug|x64.ActiveCfg = Debug|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Debug|x64.Build.0 = Debug|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Debug|x86.ActiveCfg = Debug|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Debug|x86.Build.0 = Debug|Any CPU {7E733B83-275C-4639-AA10-4A59B681B904}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E733B83-275C-4639-AA10-4A59B681B904}.Release|Any CPU.Build.0 = Release|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Release|x64.ActiveCfg = Release|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Release|x64.Build.0 = Release|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Release|x86.ActiveCfg = Release|Any CPU + {7E733B83-275C-4639-AA10-4A59B681B904}.Release|x86.Build.0 = Release|Any CPU {56107B61-262D-413A-A9B6-4F3730220415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56107B61-262D-413A-A9B6-4F3730220415}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Debug|x64.ActiveCfg = Debug|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Debug|x64.Build.0 = Debug|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Debug|x86.ActiveCfg = Debug|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Debug|x86.Build.0 = Debug|Any CPU {56107B61-262D-413A-A9B6-4F3730220415}.Release|Any CPU.ActiveCfg = Release|Any CPU {56107B61-262D-413A-A9B6-4F3730220415}.Release|Any CPU.Build.0 = Release|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Release|x64.ActiveCfg = Release|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Release|x64.Build.0 = Release|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Release|x86.ActiveCfg = Release|Any CPU + {56107B61-262D-413A-A9B6-4F3730220415}.Release|x86.Build.0 = Release|Any CPU {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Debug|x64.ActiveCfg = Debug|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Debug|x64.Build.0 = Debug|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Debug|x86.ActiveCfg = Debug|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Debug|x86.Build.0 = Debug|Any CPU {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Release|Any CPU.ActiveCfg = Release|Any CPU {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Release|Any CPU.Build.0 = Release|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Release|x64.ActiveCfg = Release|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Release|x64.Build.0 = Release|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Release|x86.ActiveCfg = Release|Any CPU + {41CA2D15-9289-4A24-A519-EFB1F7CEB633}.Release|x86.Build.0 = Release|Any CPU {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Debug|x64.Build.0 = Debug|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Debug|x86.Build.0 = Debug|Any CPU {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Release|Any CPU.Build.0 = Release|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Release|x64.ActiveCfg = Release|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Release|x64.Build.0 = Release|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Release|x86.ActiveCfg = Release|Any CPU + {1E7A5065-4B24-4B12-A0F2-7B0564989C95}.Release|x86.Build.0 = Release|Any CPU {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Debug|x64.ActiveCfg = Debug|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Debug|x64.Build.0 = Debug|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Debug|x86.ActiveCfg = Debug|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Debug|x86.Build.0 = Debug|Any CPU {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Release|Any CPU.Build.0 = Release|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Release|x64.ActiveCfg = Release|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Release|x64.Build.0 = Release|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Release|x86.ActiveCfg = Release|Any CPU + {F4E98BE4-6F95-4B4E-924D-CBAD02AF24F1}.Release|x86.Build.0 = Release|Any CPU {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Debug|x64.Build.0 = Debug|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Debug|x86.Build.0 = Debug|Any CPU {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Release|Any CPU.Build.0 = Release|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Release|x64.ActiveCfg = Release|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Release|x64.Build.0 = Release|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Release|x86.ActiveCfg = Release|Any CPU + {C8A16685-0A51-4D1A-B399-FB94C90D9BDC}.Release|x86.Build.0 = Release|Any CPU {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Debug|x64.ActiveCfg = Debug|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Debug|x64.Build.0 = Debug|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Debug|x86.Build.0 = Debug|Any CPU {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Release|Any CPU.Build.0 = Release|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Release|x64.ActiveCfg = Release|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Release|x64.Build.0 = Release|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Release|x86.ActiveCfg = Release|Any CPU + {D70F0C9A-E954-4A67-B23D-9BE22721BD5D}.Release|x86.Build.0 = Release|Any CPU {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Debug|x64.ActiveCfg = Debug|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Debug|x64.Build.0 = Debug|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Debug|x86.ActiveCfg = Debug|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Debug|x86.Build.0 = Debug|Any CPU {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Release|Any CPU.ActiveCfg = Release|Any CPU {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Release|Any CPU.Build.0 = Release|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Release|x64.ActiveCfg = Release|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Release|x64.Build.0 = Release|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Release|x86.ActiveCfg = Release|Any CPU + {663CDDFA-E15F-4356-AE01-2311C9B83D52}.Release|x86.Build.0 = Release|Any CPU {F59861D9-01D6-44C9-85A9-E6050D55D290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F59861D9-01D6-44C9-85A9-E6050D55D290}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Debug|x64.ActiveCfg = Debug|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Debug|x64.Build.0 = Debug|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Debug|x86.ActiveCfg = Debug|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Debug|x86.Build.0 = Debug|Any CPU {F59861D9-01D6-44C9-85A9-E6050D55D290}.Release|Any CPU.ActiveCfg = Release|Any CPU {F59861D9-01D6-44C9-85A9-E6050D55D290}.Release|Any CPU.Build.0 = Release|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Release|x64.ActiveCfg = Release|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Release|x64.Build.0 = Release|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Release|x86.ActiveCfg = Release|Any CPU + {F59861D9-01D6-44C9-85A9-E6050D55D290}.Release|x86.Build.0 = Release|Any CPU {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Debug|x64.Build.0 = Debug|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Debug|x86.ActiveCfg = Debug|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Debug|x86.Build.0 = Debug|Any CPU {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Release|Any CPU.Build.0 = Release|Any CPU - {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|Any CPU.Build.0 = Release|Any CPU - {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|Any CPU.Build.0 = Release|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Release|x64.ActiveCfg = Release|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Release|x64.Build.0 = Release|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Release|x86.ActiveCfg = Release|Any CPU + {CB77669F-5B48-4AC6-B20E-A928660E93F8}.Release|x86.Build.0 = Release|Any CPU {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Debug|x64.ActiveCfg = Debug|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Debug|x64.Build.0 = Debug|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Debug|x86.ActiveCfg = Debug|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Debug|x86.Build.0 = Debug|Any CPU {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Release|Any CPU.Build.0 = Release|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Release|x64.ActiveCfg = Release|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Release|x64.Build.0 = Release|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Release|x86.ActiveCfg = Release|Any CPU + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C}.Release|x86.Build.0 = Release|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|x64.Build.0 = Debug|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Debug|x86.Build.0 = Debug|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|Any CPU.Build.0 = Release|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|x64.ActiveCfg = Release|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|x64.Build.0 = Release|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|x86.ActiveCfg = Release|Any CPU + {DC61324B-D389-4A1D-B048-D0AA43A6BBE7}.Release|x86.Build.0 = Release|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|x64.ActiveCfg = Debug|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|x64.Build.0 = Debug|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|x86.ActiveCfg = Debug|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Debug|x86.Build.0 = Debug|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|Any CPU.Build.0 = Release|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|x64.ActiveCfg = Release|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|x64.Build.0 = Release|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|x86.ActiveCfg = Release|Any CPU + {03F99CE9-F952-47B0-B71A-1F4865E52443}.Release|x86.Build.0 = Release|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Debug|x64.ActiveCfg = Debug|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Debug|x64.Build.0 = Debug|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Debug|x86.ActiveCfg = Debug|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Debug|x86.Build.0 = Debug|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Release|Any CPU.Build.0 = Release|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Release|x64.ActiveCfg = Release|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Release|x64.Build.0 = Release|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Release|x86.ActiveCfg = Release|Any CPU + {5547FB9B-7AEF-49C7-AA11-119D37DD9528}.Release|x86.Build.0 = Release|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Debug|x64.ActiveCfg = Debug|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Debug|x64.Build.0 = Debug|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Debug|x86.ActiveCfg = Debug|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Debug|x86.Build.0 = Debug|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Release|Any CPU.Build.0 = Release|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Release|x64.ActiveCfg = Release|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Release|x64.Build.0 = Release|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Release|x86.ActiveCfg = Release|Any CPU + {B1380466-18E7-4CAD-88F8-E1419D2B6300}.Release|x86.Build.0 = Release|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Debug|x64.ActiveCfg = Debug|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Debug|x64.Build.0 = Debug|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Debug|x86.ActiveCfg = Debug|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Debug|x86.Build.0 = Debug|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Release|Any CPU.Build.0 = Release|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Release|x64.ActiveCfg = Release|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Release|x64.Build.0 = Release|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Release|x86.ActiveCfg = Release|Any CPU + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F}.Release|x86.Build.0 = Release|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Debug|x64.ActiveCfg = Debug|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Debug|x64.Build.0 = Debug|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Debug|x86.ActiveCfg = Debug|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Debug|x86.Build.0 = Debug|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Release|Any CPU.Build.0 = Release|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Release|x64.ActiveCfg = Release|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Release|x64.Build.0 = Release|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Release|x86.ActiveCfg = Release|Any CPU + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -99,8 +264,12 @@ Global {663CDDFA-E15F-4356-AE01-2311C9B83D52} = {CA9BF4D6-6729-4011-888E-48F5F739B469} {F59861D9-01D6-44C9-85A9-E6050D55D290} = {CA9BF4D6-6729-4011-888E-48F5F739B469} {CB77669F-5B48-4AC6-B20E-A928660E93F8} = {CA9BF4D6-6729-4011-888E-48F5F739B469} + {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C} = {CA9BF4D6-6729-4011-888E-48F5F739B469} {DC61324B-D389-4A1D-B048-D0AA43A6BBE7} = {CA9BF4D6-6729-4011-888E-48F5F739B469} {03F99CE9-F952-47B0-B71A-1F4865E52443} = {CA9BF4D6-6729-4011-888E-48F5F739B469} - {E3F6D1B7-DB78-4F36-BE77-2F9D2D7B5B7C} = {CA9BF4D6-6729-4011-888E-48F5F739B469} + {5547FB9B-7AEF-49C7-AA11-119D37DD9528} = {CA9BF4D6-6729-4011-888E-48F5F739B469} + {B1380466-18E7-4CAD-88F8-E1419D2B6300} = {CA9BF4D6-6729-4011-888E-48F5F739B469} + {B6EAE0A3-3427-4D86-B2BA-B185F476B74F} = {CA9BF4D6-6729-4011-888E-48F5F739B469} + {CCA23A57-4BC4-4C53-9A96-41FCFF5407F5} = {CA9BF4D6-6729-4011-888E-48F5F739B469} EndGlobalSection EndGlobal diff --git a/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/ConfigureServices.cs b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..860e2a2 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/ConfigureServices.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddClubMembershipProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} diff --git a/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/FrontOffice.BFF.ClubMembership.Protobuf.csproj b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/FrontOffice.BFF.ClubMembership.Protobuf.csproj new file mode 100644 index 0000000..4acdc9e --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/FrontOffice.BFF.ClubMembership.Protobuf.csproj @@ -0,0 +1,25 @@ + + + net9.0 + enable + enable + 0.0.1 + Foursat.FrontOffice.BFF.ClubMembership.Protobuf + False + False + None + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/clubmembership.proto b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/clubmembership.proto new file mode 100644 index 0000000..e2b6953 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/clubmembership.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package clubmembership; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "FrontOffice.BFF.ClubMembership.Protobuf.Protos.ClubMembership"; + +// سرویس عضویت باشگاه - برای کاربران FrontOffice +service ClubMembershipContract +{ + // دریافت وضعیت عضویت باشگاه کاربر جاری + rpc GetMyClubMembership(google.protobuf.Empty) returns (GetMyClubMembershipResponse){ + option (google.api.http) = { + get: "/ClubMembership/MyStatus" + }; + }; + + // فعال‌سازی عضویت باشگاه (پرداخت 56M) + rpc ActivateMyClubMembership(ActivateMyClubMembershipRequest) returns (ActivateMyClubMembershipResponse){ + option (google.api.http) = { + post: "/ClubMembership/Activate" + body: "*" + }; + }; +} + +// ============ GetMyClubMembership ============ +message GetMyClubMembershipResponse +{ + int64 user_id = 1; + int64 package_id = 2; + string package_name = 3; + string activation_code = 4; + bool is_active = 5; + google.protobuf.Timestamp activation_date = 6; + google.protobuf.Timestamp expiration_date = 7; + string status = 8; // Trial/Active/Expired/Inactive + int32 days_remaining = 9; + bool is_trial_period = 10; +} + +// ============ ActivateMyClubMembership ============ +message ActivateMyClubMembershipRequest +{ + int64 package_id = 1; + google.protobuf.StringValue activation_code = 2; + int32 duration_months = 3; // پیش‌فرض: 12 +} + +message ActivateMyClubMembershipResponse +{ + bool success = 1; + string message = 2; + google.protobuf.Timestamp activation_date = 3; + google.protobuf.Timestamp expiration_date = 4; + int64 amount_paid = 5; +} diff --git a/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/google/api/http.proto b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.ClubMembership.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/ConfigureServices.cs b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..a137c4b --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/ConfigureServices.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddCommissionProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} diff --git a/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/FrontOffice.BFF.Commission.Protobuf.csproj b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/FrontOffice.BFF.Commission.Protobuf.csproj new file mode 100644 index 0000000..2612d4b --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/FrontOffice.BFF.Commission.Protobuf.csproj @@ -0,0 +1,25 @@ + + + net9.0 + enable + enable + 0.0.1 + Foursat.FrontOffice.BFF.Commission.Protobuf + False + False + None + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/commission.proto b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/commission.proto new file mode 100644 index 0000000..1cabdd1 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/commission.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; + +package commission; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "FrontOffice.BFF.Commission.Protobuf.Protos.Commission"; + +// سرویس کمیسیون - برای کاربران FrontOffice +service CommissionContract +{ + // دریافت لیست پرداخت‌های کمیسیون کاربر + rpc GetMyCommissionPayouts(GetMyCommissionPayoutsRequest) returns (GetMyCommissionPayoutsResponse){ + option (google.api.http) = { + get: "/Commission/MyPayouts" + }; + }; + + // دریافت لیست بالانس‌های هفتگی کاربر + rpc GetMyWeeklyBalances(GetMyWeeklyBalancesRequest) returns (GetMyWeeklyBalancesResponse){ + option (google.api.http) = { + get: "/Commission/MyWeeklyBalances" + }; + }; +} + +// ============ MetaData ============ +message MetaData +{ + int64 total_count = 1; +} + +// ============ GetMyCommissionPayouts ============ +message GetMyCommissionPayoutsRequest +{ + int32 page_number = 1; + int32 page_size = 2; + google.protobuf.StringValue week_number = 3; + google.protobuf.Int32Value status = 4; // 0=Pending, 1=Calculated, 2=Paid, 3=Withdrawn +} + +message GetMyCommissionPayoutsResponse +{ + MetaData meta_data = 1; + repeated CommissionPayoutModel payouts = 2; +} + +message CommissionPayoutModel +{ + int64 id = 1; + string week_number = 2; + string week_label = 3; + int32 balances_earned = 4; + int64 total_amount = 5; + string amount_formatted = 6; + string status = 7; + string status_badge_color = 8; + google.protobuf.Timestamp calculated_date = 9; + string date_persian = 10; +} + +// ============ GetMyWeeklyBalances ============ +message GetMyWeeklyBalancesRequest +{ + int32 page_number = 1; + int32 page_size = 2; + google.protobuf.StringValue week_number = 3; + bool only_active = 4; +} + +message GetMyWeeklyBalancesResponse +{ + MetaData meta_data = 1; + repeated WeeklyBalanceModel balances = 2; + int32 total_left_balances = 3; + int32 total_right_balances = 4; + string weaker_leg = 5; +} + +message WeeklyBalanceModel +{ + int64 id = 1; + string week_number = 2; + int32 left_leg_balances = 3; + int32 right_leg_balances = 4; + int32 total_balances = 5; + int64 weekly_pool_contribution = 6; + bool is_expired = 7; + google.protobuf.Timestamp calculated_at = 8; + string date_persian = 9; +} diff --git a/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/google/api/http.proto b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.Commission.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/ConfigureServices.cs b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..e907775 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/ConfigureServices.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddDiscountShopProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} diff --git a/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/FrontOffice.BFF.DiscountShop.Protobuf.csproj b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/FrontOffice.BFF.DiscountShop.Protobuf.csproj new file mode 100644 index 0000000..3390c03 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/FrontOffice.BFF.DiscountShop.Protobuf.csproj @@ -0,0 +1,25 @@ + + + net9.0 + enable + enable + 0.0.1 + Foursat.FrontOffice.BFF.DiscountShop.Protobuf + False + False + None + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/discountshop.proto b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/discountshop.proto new file mode 100644 index 0000000..70392a2 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/discountshop.proto @@ -0,0 +1,190 @@ +syntax = "proto3"; + +package discountshop; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop"; + +// سرویس فروشگاه تخفیفی - برای کاربران عضو باشگاه +service DiscountShopContract +{ + // محصولات + rpc GetDiscountProducts(GetDiscountProductsRequest) returns (GetDiscountProductsResponse){ + option (google.api.http) = { + get: "/DiscountShop/Products" + }; + }; + + // دسته‌بندی‌ها + rpc GetDiscountCategories(GetDiscountCategoriesRequest) returns (GetDiscountCategoriesResponse){ + option (google.api.http) = { + get: "/DiscountShop/Categories" + }; + }; + + // سبد خرید + rpc GetMyDiscountCart(google.protobuf.Empty) returns (GetMyDiscountCartResponse){ + option (google.api.http) = { + get: "/DiscountShop/Cart" + }; + }; + + rpc AddToDiscountCart(AddToDiscountCartRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/DiscountShop/Cart/Add" + body: "*" + }; + }; + + rpc RemoveFromDiscountCart(RemoveFromDiscountCartRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/DiscountShop/Cart/Remove" + body: "*" + }; + }; + + // سفارشات + rpc GetMyDiscountOrders(GetMyDiscountOrdersRequest) returns (GetMyDiscountOrdersResponse){ + option (google.api.http) = { + get: "/DiscountShop/Orders" + }; + }; + + rpc PlaceDiscountOrder(PlaceDiscountOrderRequest) returns (PlaceDiscountOrderResponse){ + option (google.api.http) = { + post: "/DiscountShop/Orders/Place" + body: "*" + }; + }; +} + +// ============ MetaData ============ +message MetaData +{ + int64 total_count = 1; +} + +// ============ Products ============ +message GetDiscountProductsRequest +{ + int32 page_number = 1; + int32 page_size = 2; + google.protobuf.StringValue search_term = 3; + google.protobuf.Int64Value category_id = 4; +} + +message GetDiscountProductsResponse +{ + MetaData meta_data = 1; + repeated DiscountProductModel products = 2; +} + +message DiscountProductModel +{ + int64 id = 1; + string title = 2; + string description = 3; + string image_path = 4; + int64 original_price = 5; + int64 discounted_price = 6; + int32 discount_percent = 7; + int32 remaining_count = 8; + bool is_available = 9; +} + +// ============ Categories ============ +message GetDiscountCategoriesRequest +{ + google.protobuf.Int64Value parent_category_id = 1; + bool only_active = 2; +} + +message GetDiscountCategoriesResponse +{ + repeated DiscountCategoryModel categories = 1; +} + +message DiscountCategoryModel +{ + int64 id = 1; + string title = 2; + google.protobuf.Int64Value parent_id = 3; + string icon_path = 4; + int32 sort_order = 5; + repeated DiscountCategoryModel children = 6; +} + +// ============ Cart ============ +message GetMyDiscountCartResponse +{ + repeated DiscountCartItemModel items = 1; + int64 total_price = 2; + int64 total_discount_amount = 3; +} + +message DiscountCartItemModel +{ + int64 product_id = 1; + string product_title = 2; + string product_image_path = 3; + int64 unit_price = 4; + int32 count = 5; + int64 total_price = 6; +} + +message AddToDiscountCartRequest +{ + int64 product_id = 1; + int32 count = 2; +} + +message RemoveFromDiscountCartRequest +{ + int64 product_id = 1; +} + +// ============ Orders ============ +message GetMyDiscountOrdersRequest +{ + int32 page_number = 1; + int32 page_size = 2; +} + +message GetMyDiscountOrdersResponse +{ + MetaData meta_data = 1; + repeated DiscountOrderModel orders = 2; +} + +message DiscountOrderModel +{ + int64 id = 1; + string order_number = 2; + string status = 3; + string status_color = 4; + int64 total_amount = 5; + int64 discount_used = 6; + int64 gateway_paid = 7; + google.protobuf.Timestamp created_at = 8; + string created_at_persian = 9; +} + +message PlaceDiscountOrderRequest +{ + int64 user_address_id = 1; + int64 discount_balance_to_use = 2; + google.protobuf.StringValue notes = 3; +} + +message PlaceDiscountOrderResponse +{ + bool success = 1; + string message = 2; + int64 order_id = 3; + string order_number = 4; + google.protobuf.StringValue payment_url = 5; +} diff --git a/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/google/api/http.proto b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.DiscountShop.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/ConfigureServices.cs b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/ConfigureServices.cs new file mode 100644 index 0000000..03ac326 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/ConfigureServices.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using System.Reflection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ConfigureServices +{ + public static IServiceCollection AddNetworkMembershipProtobufServices(this IServiceCollection services) + { + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + return services; + } +} diff --git a/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/FrontOffice.BFF.NetworkMembership.Protobuf.csproj b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/FrontOffice.BFF.NetworkMembership.Protobuf.csproj new file mode 100644 index 0000000..b0ba280 --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/FrontOffice.BFF.NetworkMembership.Protobuf.csproj @@ -0,0 +1,25 @@ + + + net9.0 + enable + enable + 0.0.1 + Foursat.FrontOffice.BFF.NetworkMembership.Protobuf + False + False + None + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/google/api/annotations.proto b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/google/api/http.proto b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/google/api/http.proto new file mode 100644 index 0000000..b8426ba --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/google/api/http.proto @@ -0,0 +1,377 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} + diff --git a/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/networkmembership.proto b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/networkmembership.proto new file mode 100644 index 0000000..f2edf8f --- /dev/null +++ b/src/Protobufs/FrontOffice.BFF.NetworkMembership.Protobuf/Protos/networkmembership.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +package networkmembership; + +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership"; + +// سرویس عضویت شبکه‌ای - برای کاربران FrontOffice +service NetworkMembershipContract +{ + // دریافت درخت شبکه کاربر جاری + rpc GetMyNetworkTree(GetMyNetworkTreeRequest) returns (GetMyNetworkTreeResponse){ + option (google.api.http) = { + get: "/NetworkMembership/MyTree" + }; + }; + + // دریافت آمار شبکه کاربر جاری + rpc GetMyNetworkStatistics(google.protobuf.Empty) returns (GetMyNetworkStatisticsResponse){ + option (google.api.http) = { + get: "/NetworkMembership/MyStats" + }; + }; + + // دریافت موقعیت کاربر در شبکه + rpc GetMyNetworkPosition(google.protobuf.Empty) returns (GetMyNetworkPositionResponse){ + option (google.api.http) = { + get: "/NetworkMembership/MyPosition" + }; + }; +} + +// ============ GetMyNetworkTree ============ +message GetMyNetworkTreeRequest +{ + int32 max_depth = 1; // حداکثر عمق درخت (پیش‌فرض: 3) +} + +message GetMyNetworkTreeResponse +{ + NetworkNodeModel root_node = 1; + int32 total_members = 2; + int32 current_depth = 3; +} + +message NetworkNodeModel +{ + int64 user_id = 1; + string full_name = 2; + string mobile = 3; + google.protobuf.StringValue avatar = 4; + string position = 5; // Root/Left/Right + NetworkNodeModel left_child = 6; + NetworkNodeModel right_child = 7; + int32 level = 8; + bool has_children = 9; +} + +// ============ GetMyNetworkStatistics ============ +message GetMyNetworkStatisticsResponse +{ + int32 total_members = 1; + int32 active_members = 2; + int32 left_leg_count = 3; + int32 right_leg_count = 4; + double left_percentage = 5; + double right_percentage = 6; + double average_depth = 7; + int32 max_depth = 8; + string weaker_leg = 9; + int32 my_network_level = 10; + string my_network_leg = 11; + string my_referral_code = 12; + LastMemberModel last_member = 13; +} + +message LastMemberModel +{ + int64 user_id = 1; + string full_name = 2; + string position = 3; + int32 total_children = 4; +} + +// ============ GetMyNetworkPosition ============ +message GetMyNetworkPositionResponse +{ + int64 user_id = 1; + int64 parent_id = 2; + string parent_name = 3; + string position = 4; // Left/Right + int32 level = 5; + string referral_code = 6; + google.protobuf.Timestamp joined_at = 7; + bool has_left_child = 8; + bool has_right_child = 9; +}