feat: Add Protobuf definitions for Network Membership service

- Introduced `networkmembership.proto` with RPC methods for retrieving user network tree, statistics, and position.
- Implemented HTTP annotations for gRPC transcoding in the service methods.
- Added support for Google API annotations in `annotations.proto` and `http.proto`.
- Created `ConfigureServices.cs` to register FluentValidation for the Protobuf services.
- Updated project file to include necessary dependencies for gRPC and Protobuf.
This commit is contained in:
masoodafar-web
2025-12-04 19:53:47 +03:30
parent 75e446f80f
commit 9a42060653
55 changed files with 3729 additions and 16 deletions

View File

@@ -79,4 +79,9 @@ public class WeeklyBalanceItemDto
/// آیا منقضی شده؟
/// </summary>
public bool IsExpired { get; set; }
/// <summary>
/// تاریخ شمسی
/// </summary>
public string DatePersian { get; set; } = string.Empty;
}

View File

@@ -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

View File

@@ -0,0 +1,23 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.AddToDiscountCart;
/// <summary>
/// افزودن محصول به سبد خرید تخفیفی
/// </summary>
public record AddToDiscountCartCommand : IRequest<AddToDiscountCartResponseDto>
{
/// <summary>
/// شناسه محصول
/// </summary>
public long ProductId { get; init; }
/// <summary>
/// تعداد
/// </summary>
public int Count { get; init; } = 1;
}
public class AddToDiscountCartResponseDto
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,33 @@
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.AddToDiscountCart;
public class AddToDiscountCartCommandHandler : IRequestHandler<AddToDiscountCartCommand, AddToDiscountCartResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public AddToDiscountCartCommandHandler(IApplicationContractContext context, ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<AddToDiscountCartResponseDto> 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
};
}
}

View File

@@ -0,0 +1,39 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.PlaceDiscountOrder;
/// <summary>
/// ثبت سفارش از سبد خرید تخفیفی
/// </summary>
public record PlaceDiscountOrderCommand : IRequest<PlaceDiscountOrderResponseDto>
{
/// <summary>
/// شناسه آدرس کاربر
/// </summary>
public long UserAddressId { get; init; }
/// <summary>
/// مقدار استفاده از موجودی تخفیف (اختیاری)
/// </summary>
public long DiscountBalanceToUse { get; init; }
/// <summary>
/// توضیحات
/// </summary>
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; }
/// <summary>
/// مبلغ قابل پرداخت از درگاه
/// </summary>
public long GatewayAmount { get; set; }
/// <summary>
/// لینک پرداخت (اگر نیاز باشد)
/// </summary>
public string? PaymentUrl { get; set; }
}

View File

@@ -0,0 +1,41 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.PlaceDiscountOrder;
public class PlaceDiscountOrderCommandHandler : IRequestHandler<PlaceDiscountOrderCommand, PlaceDiscountOrderResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public PlaceDiscountOrderCommandHandler(IApplicationContractContext context, ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<PlaceDiscountOrderResponseDto> 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
};
}
}

View File

@@ -0,0 +1,18 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.RemoveFromDiscountCart;
/// <summary>
/// حذف محصول از سبد خرید تخفیفی
/// </summary>
public record RemoveFromDiscountCartCommand : IRequest<RemoveFromDiscountCartResponseDto>
{
/// <summary>
/// شناسه محصول
/// </summary>
public long ProductId { get; init; }
}
public class RemoveFromDiscountCartResponseDto
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,32 @@
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
namespace FrontOffice.BFF.Application.DiscountShopCQ.Commands.RemoveFromDiscountCart;
public class RemoveFromDiscountCartCommandHandler : IRequestHandler<RemoveFromDiscountCartCommand, RemoveFromDiscountCartResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public RemoveFromDiscountCartCommandHandler(IApplicationContractContext context, ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<RemoveFromDiscountCartResponseDto> 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
};
}
}

View File

@@ -0,0 +1,17 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories;
/// <summary>
/// دریافت لیست دسته‌بندی‌های فروشگاه تخفیفی
/// </summary>
public record GetDiscountCategoriesQuery : IRequest<GetDiscountCategoriesResponseDto>
{
/// <summary>
/// شناسه دسته‌بندی والد (اختیاری)
/// </summary>
public long? ParentCategoryId { get; init; }
/// <summary>
/// فقط فعال‌ها
/// </summary>
public bool OnlyActive { get; init; } = true;
}

View File

@@ -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<GetDiscountCategoriesQuery, GetDiscountCategoriesResponseDto>
{
private readonly IApplicationContractContext _context;
public GetDiscountCategoriesQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetDiscountCategoriesResponseDto> 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()
};
}
}

View File

@@ -0,0 +1,54 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountCategories;
public class GetDiscountCategoriesResponseDto
{
public List<DiscountCategoryDto> Categories { get; set; } = new();
}
public class DiscountCategoryDto
{
/// <summary>
/// شناسه
/// </summary>
public long Id { get; set; }
/// <summary>
/// نام
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// عنوان
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// توضیحات
/// </summary>
public string? Description { get; set; }
/// <summary>
/// مسیر تصویر
/// </summary>
public string? ImagePath { get; set; }
/// <summary>
/// شناسه والد
/// </summary>
public long? ParentCategoryId { get; set; }
/// <summary>
/// ترتیب نمایش
/// </summary>
public int SortOrder { get; set; }
/// <summary>
/// فعال بودن
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// زیرمجموعه‌ها
/// </summary>
public List<DiscountCategoryDto> Children { get; set; } = new();
}

View File

@@ -0,0 +1,27 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts;
/// <summary>
/// دریافت لیست محصولات فروشگاه تخفیفی
/// </summary>
public record GetDiscountProductsQuery : IRequest<GetDiscountProductsResponseDto>
{
/// <summary>
/// شناسه دسته‌بندی (اختیاری)
/// </summary>
public long? CategoryId { get; init; }
/// <summary>
/// شماره صفحه
/// </summary>
public int PageNumber { get; init; } = 1;
/// <summary>
/// تعداد در هر صفحه
/// </summary>
public int PageSize { get; init; } = 20;
/// <summary>
/// عبارت جستجو (اختیاری)
/// </summary>
public string? SearchTerm { get; init; }
}

View File

@@ -0,0 +1,50 @@
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts;
public class GetDiscountProductsQueryHandler : IRequestHandler<GetDiscountProductsQuery, GetDiscountProductsResponseDto>
{
private readonly IApplicationContractContext _context;
public GetDiscountProductsQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetDiscountProductsResponseDto> 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()
};
}
}

View File

@@ -0,0 +1,65 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetDiscountProducts;
public class GetDiscountProductsResponseDto
{
public MetaData MetaData { get; set; } = new();
public List<DiscountProductDto> Products { get; set; } = new();
}
public class DiscountProductDto
{
/// <summary>
/// شناسه محصول
/// </summary>
public long Id { get; set; }
/// <summary>
/// عنوان
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// توضیحات کوتاه
/// </summary>
public string ShortInformation { get; set; } = string.Empty;
/// <summary>
/// قیمت اصلی
/// </summary>
public long Price { get; set; }
/// <summary>
/// حداکثر درصد تخفیف
/// </summary>
public int MaxDiscountPercent { get; set; }
/// <summary>
/// قیمت با تخفیف
/// </summary>
public long DiscountedPrice => Price - (Price * MaxDiscountPercent / 100);
/// <summary>
/// مسیر تصویر
/// </summary>
public string ImagePath { get; set; } = string.Empty;
/// <summary>
/// مسیر تصویر کوچک
/// </summary>
public string ThumbnailPath { get; set; } = string.Empty;
/// <summary>
/// موجودی
/// </summary>
public int AvailableCount { get; set; }
/// <summary>
/// فعال بودن
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// دسته‌بندی‌ها
/// </summary>
public List<long> CategoryIds { get; set; } = new();
}

View File

@@ -0,0 +1,6 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart;
/// <summary>
/// دریافت سبد خرید فروشگاه تخفیفی کاربر جاری
/// </summary>
public record GetMyDiscountCartQuery : IRequest<GetMyDiscountCartResponseDto>;

View File

@@ -0,0 +1,42 @@
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart;
public class GetMyDiscountCartQueryHandler : IRequestHandler<GetMyDiscountCartQuery, GetMyDiscountCartResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public GetMyDiscountCartQueryHandler(IApplicationContractContext context, ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<GetMyDiscountCartResponseDto> 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
};
}
}

View File

@@ -0,0 +1,77 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountCart;
public class GetMyDiscountCartResponseDto
{
/// <summary>
/// آیتم‌های سبد خرید
/// </summary>
public List<DiscountCartItemDto> Items { get; set; } = new();
/// <summary>
/// جمع کل قیمت (بدون تخفیف)
/// </summary>
public long TotalPrice { get; set; }
/// <summary>
/// جمع کل تخفیف
/// </summary>
public long TotalDiscountAmount { get; set; }
/// <summary>
/// مبلغ قابل پرداخت
/// </summary>
public long PayableAmount => TotalPrice - TotalDiscountAmount;
/// <summary>
/// تعداد کل آیتم‌ها
/// </summary>
public int TotalItemCount => Items.Sum(i => i.Count);
}
public class DiscountCartItemDto
{
/// <summary>
/// شناسه آیتم سبد
/// </summary>
public long Id { get; set; }
/// <summary>
/// شناسه محصول
/// </summary>
public long ProductId { get; set; }
/// <summary>
/// عنوان محصول
/// </summary>
public string ProductTitle { get; set; } = string.Empty;
/// <summary>
/// قیمت واحد
/// </summary>
public long UnitPrice { get; set; }
/// <summary>
/// تعداد
/// </summary>
public int Count { get; set; }
/// <summary>
/// درصد تخفیف
/// </summary>
public int MaxDiscountPercent { get; set; }
/// <summary>
/// مبلغ تخفیف
/// </summary>
public long DiscountAmount { get; set; }
/// <summary>
/// قیمت کل ردیف
/// </summary>
public long TotalPrice => UnitPrice * Count;
/// <summary>
/// مسیر تصویر
/// </summary>
public string ThumbnailPath { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,10 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders;
/// <summary>
/// دریافت سفارشات فروشگاه تخفیفی کاربر
/// </summary>
public record GetMyDiscountOrdersQuery : IRequest<GetMyDiscountOrdersResponseDto>
{
public int PageNumber { get; init; } = 1;
public int PageSize { get; init; } = 20;
}

View File

@@ -0,0 +1,80 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders;
public class GetMyDiscountOrdersQueryHandler : IRequestHandler<GetMyDiscountOrdersQuery, GetMyDiscountOrdersResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public GetMyDiscountOrdersQueryHandler(IApplicationContractContext context, ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<GetMyDiscountOrdersResponseDto> 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");
}
}

View File

@@ -0,0 +1,55 @@
namespace FrontOffice.BFF.Application.DiscountShopCQ.Queries.GetMyDiscountOrders;
public class GetMyDiscountOrdersResponseDto
{
public MetaData MetaData { get; set; } = new();
public List<DiscountOrderDto> Orders { get; set; } = new();
}
public class DiscountOrderDto
{
/// <summary>
/// شناسه سفارش
/// </summary>
public long Id { get; set; }
/// <summary>
/// شماره سفارش
/// </summary>
public string OrderNumber { get; set; } = string.Empty;
/// <summary>
/// وضعیت
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// رنگ وضعیت
/// </summary>
public string StatusColor { get; set; } = string.Empty;
/// <summary>
/// جمع کل
/// </summary>
public long TotalAmount { get; set; }
/// <summary>
/// تخفیف استفاده شده
/// </summary>
public long DiscountUsed { get; set; }
/// <summary>
/// مبلغ پرداخت شده از درگاه
/// </summary>
public long GatewayPaid { get; set; }
/// <summary>
/// تاریخ ثبت
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// تاریخ شمسی
/// </summary>
public string CreatedAtPersian { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,3 @@
namespace FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition;
public record GetMyNetworkPositionQuery : IRequest<GetMyNetworkPositionResponseDto>;

View File

@@ -0,0 +1,44 @@
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
namespace FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition;
public class GetMyNetworkPositionQueryHandler : IRequestHandler<GetMyNetworkPositionQuery, GetMyNetworkPositionResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ICurrentUserService _currentUserService;
public GetMyNetworkPositionQueryHandler(
IApplicationContractContext context,
ICurrentUserService currentUserService)
{
_context = context;
_currentUserService = currentUserService;
}
public async Task<GetMyNetworkPositionResponseDto> 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
};
}
}

View File

@@ -0,0 +1,49 @@
namespace FrontOffice.BFF.Application.NetworkMembershipCQ.Queries.GetMyNetworkPosition;
public class GetMyNetworkPositionResponseDto
{
/// <summary>
/// شناسه کاربر
/// </summary>
public long UserId { get; set; }
/// <summary>
/// شناسه والد
/// </summary>
public long ParentId { get; set; }
/// <summary>
/// نام والد
/// </summary>
public string ParentName { get; set; } = string.Empty;
/// <summary>
/// موقعیت در درخت (Left/Right)
/// </summary>
public string Position { get; set; } = string.Empty;
/// <summary>
/// سطح در درخت
/// </summary>
public int Level { get; set; }
/// <summary>
/// کد معرف
/// </summary>
public string ReferralCode { get; set; } = string.Empty;
/// <summary>
/// تاریخ عضویت
/// </summary>
public DateTime JoinedAt { get; set; }
/// <summary>
/// آیا فرزند چپ دارد؟
/// </summary>
public bool HasLeftChild { get; set; }
/// <summary>
/// آیا فرزند راست دارد؟
/// </summary>
public bool HasRightChild { get; set; }
}

View File

@@ -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<ClubMembershipContract.ClubMembershipContractClient>();
public NetworkMembershipContract.NetworkMembershipContractClient NetworkMemberships => GetService<NetworkMembershipContract.NetworkMembershipContractClient>();
// Discount Shop System
public DiscountProductContract.DiscountProductContractClient DiscountProducts => GetService<DiscountProductContract.DiscountProductContractClient>();
public DiscountCategoryContract.DiscountCategoryContractClient DiscountCategories => GetService<DiscountCategoryContract.DiscountCategoryContractClient>();
public DiscountShoppingCartContract.DiscountShoppingCartContractClient DiscountCart => GetService<DiscountShoppingCartContract.DiscountShoppingCartContractClient>();
public DiscountOrderContract.DiscountOrderContractClient DiscountOrders => GetService<DiscountOrderContract.DiscountOrderContractClient>();
#endregion
#region PYMS

View File

@@ -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<ProtoDto.ActivateMyClubMembershipRequest, ActivateMyClubMembershipCommand>()
.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<GetMyClubMembershipResponseDto, ProtoDto.GetMyClubMembershipResponse>()
.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<ActivateMyClubMembershipResponseDto, ProtoDto.ActivateMyClubMembershipResponse>()
.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);
}
}

View File

@@ -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<ProtoDto.GetMyCommissionPayoutsRequest, GetMyCommissionPayoutsQuery>()
.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<ProtoDto.GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery>()
.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<GetMyCommissionPayoutsResponseDto, ProtoDto.GetMyCommissionPayoutsResponse>()
.Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.TotalCount })
.Map(dest => dest.Payouts, src => src.Payouts);
config.NewConfig<CommissionPayoutDto, ProtoDto.CommissionPayoutModel>()
.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<GetMyWeeklyBalancesResponseDto, ProtoDto.GetMyWeeklyBalancesResponse>()
.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<WeeklyBalanceItemDto, ProtoDto.WeeklyBalanceModel>()
.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);
}
}

View File

@@ -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<ProtoDto.GetDiscountProductsRequest, GetDiscountProductsQuery>()
.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<ProtoDto.GetDiscountCategoriesRequest, GetDiscountCategoriesQuery>()
.Map(dest => dest.ParentCategoryId, src => src.ParentCategoryId)
.Map(dest => dest.OnlyActive, src => src.OnlyActive);
config.NewConfig<ProtoDto.GetMyDiscountOrdersRequest, GetMyDiscountOrdersQuery>()
.Map(dest => dest.PageNumber, src => src.PageNumber)
.Map(dest => dest.PageSize, src => src.PageSize);
config.NewConfig<ProtoDto.AddToDiscountCartRequest, AddToDiscountCartCommand>()
.Map(dest => dest.ProductId, src => src.ProductId)
.Map(dest => dest.Count, src => src.Count);
config.NewConfig<ProtoDto.RemoveFromDiscountCartRequest, RemoveFromDiscountCartCommand>()
.Map(dest => dest.ProductId, src => src.ProductId);
config.NewConfig<ProtoDto.PlaceDiscountOrderRequest, PlaceDiscountOrderCommand>()
.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<GetDiscountProductsResponseDto, ProtoDto.GetDiscountProductsResponse>()
.Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.MetaData.TotalCount })
.Map(dest => dest.Products, src => src.Products);
config.NewConfig<DiscountProductDto, ProtoDto.DiscountProductModel>()
.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<GetDiscountCategoriesResponseDto, ProtoDto.GetDiscountCategoriesResponse>()
.Map(dest => dest.Categories, src => src.Categories);
config.NewConfig<DiscountCategoryDto, ProtoDto.DiscountCategoryModel>()
.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<GetMyDiscountCartResponseDto, ProtoDto.GetMyDiscountCartResponse>()
.Map(dest => dest.Items, src => src.Items)
.Map(dest => dest.TotalPrice, src => src.TotalPrice)
.Map(dest => dest.TotalDiscountAmount, src => src.TotalDiscountAmount);
config.NewConfig<DiscountCartItemDto, ProtoDto.DiscountCartItemModel>()
.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<GetMyDiscountOrdersResponseDto, ProtoDto.GetMyDiscountOrdersResponse>()
.Map(dest => dest.MetaData, src => new ProtoDto.MetaData { TotalCount = src.MetaData.TotalCount })
.Map(dest => dest.Orders, src => src.Orders);
config.NewConfig<DiscountOrderDto, ProtoDto.DiscountOrderModel>()
.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<PlaceDiscountOrderResponseDto, ProtoDto.PlaceDiscountOrderResponse>()
.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);
}
}

View File

@@ -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<ProtoDto.GetMyNetworkTreeRequest, GetMyNetworkTreeQuery>()
.Map(dest => dest.MaxDepth, src => src.MaxDepth > 0 ? src.MaxDepth : 3);
// Response mappings - Tree
config.NewConfig<GetMyNetworkTreeResponseDto, ProtoDto.GetMyNetworkTreeResponse>()
.Map(dest => dest.RootNode, src => src.RootNode)
.Map(dest => dest.TotalMembers, src => src.TotalMembers)
.Map(dest => dest.CurrentDepth, src => src.CurrentDepth);
config.NewConfig<NetworkNodeDto, ProtoDto.NetworkNodeModel>()
.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<GetMyNetworkStatisticsResponseDto, ProtoDto.GetMyNetworkStatisticsResponse>()
.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<LastMemberDto, ProtoDto.LastMemberModel>()
.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<GetMyNetworkPositionResponseDto, ProtoDto.GetMyNetworkPositionResponse>()
.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);
}
}

View File

@@ -31,5 +31,9 @@
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.UserAddress.Protobuf\FrontOffice.BFF.UserAddress.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.Package.Protobuf\FrontOffice.BFF.Package.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.UserOrder.Protobuf\FrontOffice.BFF.UserOrder.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.DiscountShop.Protobuf\FrontOffice.BFF.DiscountShop.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.Commission.Protobuf\FrontOffice.BFF.Commission.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.ClubMembership.Protobuf\FrontOffice.BFF.ClubMembership.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\FrontOffice.BFF.NetworkMembership.Protobuf\FrontOffice.BFF.NetworkMembership.Protobuf.csproj" />
</ItemGroup>
</Project>

View File

@@ -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;
/// <summary>
/// سرویس عضویت باشگاه - برای کاربران FrontOffice
/// </summary>
public class ClubMembershipGrpcService : ClubMembershipContract.ClubMembershipContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public ClubMembershipGrpcService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
/// <summary>
/// دریافت وضعیت عضویت باشگاه کاربر جاری
/// </summary>
public override async Task<GetMyClubMembershipResponse> GetMyClubMembership(Empty request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyClubMembershipQuery, GetMyClubMembershipResponse>(context);
}
/// <summary>
/// فعال‌سازی عضویت باشگاه (پرداخت 56M)
/// </summary>
public override async Task<ActivateMyClubMembershipResponse> ActivateMyClubMembership(ActivateMyClubMembershipRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<ActivateMyClubMembershipRequest, ActivateMyClubMembershipCommand, ActivateMyClubMembershipResponse>(request, context);
}
}

View File

@@ -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;
/// </summary>
public class ClubMembershipService
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
private readonly ISender _sender;
public ClubMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS)
public ClubMembershipService(ISender sender)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
}
/// <summary>
/// دریافت وضعیت عضویت باشگاه کاربر جاری
/// </summary>
public async Task<GetMyClubMembershipResponseDto> GetMyClubMembership(Empty request, ServerCallContext context)
public async Task<GetMyClubMembershipResponseDto> GetMyClubMembership(CancellationToken cancellationToken = default)
{
return await _dispatchRequestToCQRS.Handle<GetMyClubMembershipQuery, GetMyClubMembershipResponseDto>(context);
return await _sender.Send(new GetMyClubMembershipQuery(), cancellationToken);
}
/// <summary>
@@ -31,8 +33,8 @@ public class ClubMembershipService
/// </summary>
public async Task<ActivateMyClubMembershipResponseDto> ActivateMyClubMembership(
ActivateMyClubMembershipCommand request,
ServerCallContext context)
CancellationToken cancellationToken = default)
{
return await _dispatchRequestToCQRS.Handle<ActivateMyClubMembershipCommand, ActivateMyClubMembershipResponseDto>(request, context);
return await _sender.Send(request, cancellationToken);
}
}

View File

@@ -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;
/// <summary>
/// سرویس کمیسیون - برای کاربران FrontOffice
/// </summary>
public class CommissionService : CommissionContract.CommissionContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public CommissionService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
/// <summary>
/// دریافت لیست پرداخت‌های کمیسیون کاربر
/// </summary>
public override async Task<GetMyCommissionPayoutsResponse> GetMyCommissionPayouts(GetMyCommissionPayoutsRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyCommissionPayoutsRequest, GetMyCommissionPayoutsQuery, GetMyCommissionPayoutsResponse>(request, context);
}
/// <summary>
/// دریافت لیست بالانس‌های هفتگی کاربر
/// </summary>
public override async Task<GetMyWeeklyBalancesResponse> GetMyWeeklyBalances(GetMyWeeklyBalancesRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery, GetMyWeeklyBalancesResponse>(request, context);
}
}

View File

@@ -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;
/// <summary>
/// سرویس فروشگاه تخفیفی - برای کاربران عضو باشگاه
/// </summary>
public class DiscountShopService : DiscountShopContract.DiscountShopContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public DiscountShopService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
/// <summary>
/// دریافت لیست محصولات تخفیفی
/// </summary>
public override async Task<GetDiscountProductsResponse> GetDiscountProducts(GetDiscountProductsRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountProductsRequest, GetDiscountProductsQuery, GetDiscountProductsResponse>(request, context);
}
/// <summary>
/// دریافت دسته‌بندی‌های فروشگاه تخفیفی
/// </summary>
public override async Task<GetDiscountCategoriesResponse> GetDiscountCategories(GetDiscountCategoriesRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetDiscountCategoriesRequest, GetDiscountCategoriesQuery, GetDiscountCategoriesResponse>(request, context);
}
/// <summary>
/// دریافت سبد خرید تخفیفی کاربر جاری
/// </summary>
public override async Task<GetMyDiscountCartResponse> GetMyDiscountCart(Empty request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyDiscountCartQuery, GetMyDiscountCartResponse>(context);
}
/// <summary>
/// افزودن محصول به سبد خرید تخفیفی
/// </summary>
public override async Task<Empty> AddToDiscountCart(AddToDiscountCartRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AddToDiscountCartRequest, AddToDiscountCartCommand>(request, context);
}
/// <summary>
/// حذف محصول از سبد خرید تخفیفی
/// </summary>
public override async Task<Empty> RemoveFromDiscountCart(RemoveFromDiscountCartRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<RemoveFromDiscountCartRequest, RemoveFromDiscountCartCommand>(request, context);
}
/// <summary>
/// دریافت لیست سفارشات تخفیفی کاربر جاری
/// </summary>
public override async Task<GetMyDiscountOrdersResponse> GetMyDiscountOrders(GetMyDiscountOrdersRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyDiscountOrdersRequest, GetMyDiscountOrdersQuery, GetMyDiscountOrdersResponse>(request, context);
}
/// <summary>
/// ثبت سفارش تخفیفی
/// </summary>
public override async Task<PlaceDiscountOrderResponse> PlaceDiscountOrder(PlaceDiscountOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<PlaceDiscountOrderRequest, PlaceDiscountOrderCommand, PlaceDiscountOrderResponse>(request, context);
}
}

View File

@@ -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;
/// <summary>
/// سرویس عضویت شبکه‌ای - برای کاربران FrontOffice
/// </summary>
public class NetworkMembershipService : NetworkMembershipContract.NetworkMembershipContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
public NetworkMembershipService(IDispatchRequestToCQRS dispatchRequestToCQRS)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
/// <summary>
/// دریافت درخت شبکه کاربر جاری
/// </summary>
public override async Task<GetMyNetworkTreeResponse> GetMyNetworkTree(GetMyNetworkTreeRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyNetworkTreeRequest, GetMyNetworkTreeQuery, GetMyNetworkTreeResponse>(request, context);
}
/// <summary>
/// دریافت آمار شبکه کاربر جاری
/// </summary>
public override async Task<GetMyNetworkStatisticsResponse> GetMyNetworkStatistics(Empty request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyNetworkStatisticsQuery, GetMyNetworkStatisticsResponse>(context);
}
/// <summary>
/// دریافت موقعیت کاربر در شبکه
/// </summary>
public override async Task<GetMyNetworkPositionResponse> GetMyNetworkPosition(Empty request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetMyNetworkPositionQuery, GetMyNetworkPositionResponse>(context);
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.1</Version>
<PackageId>Foursat.FrontOffice.BFF.ClubMembership.Protobuf</PackageId>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
<PackageReference Include="Grpc.Core.Api" Version="2.54.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.2.2" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.10.0" />
<PackageReference Include="Grpc.Tools" Version="2.55.1" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\clubmembership.proto" ProtoRoot="Protos\" GrpcServices="Both" />
</ItemGroup>
</Project>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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&param=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;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.1</Version>
<PackageId>Foursat.FrontOffice.BFF.Commission.Protobuf</PackageId>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
<PackageReference Include="Grpc.Core.Api" Version="2.54.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.2.2" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.10.0" />
<PackageReference Include="Grpc.Tools" Version="2.55.1" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\commission.proto" ProtoRoot="Protos\" GrpcServices="Both" />
</ItemGroup>
</Project>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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&param=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;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.1</Version>
<PackageId>Foursat.FrontOffice.BFF.DiscountShop.Protobuf</PackageId>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
<PackageReference Include="Grpc.Core.Api" Version="2.54.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.2.2" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.10.0" />
<PackageReference Include="Grpc.Tools" Version="2.55.1" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\discountshop.proto" ProtoRoot="Protos\" GrpcServices="Both" />
</ItemGroup>
</Project>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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&param=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;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.1</Version>
<PackageId>Foursat.FrontOffice.BFF.NetworkMembership.Protobuf</PackageId>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.23.3" />
<PackageReference Include="Grpc.Core.Api" Version="2.54.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.2.2" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.10.0" />
<PackageReference Include="Grpc.Tools" Version="2.55.1" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\networkmembership.proto" ProtoRoot="Protos\" GrpcServices="Both" />
</ItemGroup>
</Project>

View File

@@ -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;
}

View File

@@ -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&param=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;
}

View File

@@ -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;
}