feat: Implement file management and authorization features
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
- Add RequiresPermissionAttribute for gRPC method access control. - Create IFileManagementService interface for file upload and management. - Implement AddProductImageCommand and handler for adding product images. - Implement CreateNewProductsCommand and handler for creating new products with image uploads. - Implement DeleteProductsCommand and handler for deleting products and their associations. - Implement RemoveProductImageCommand and handler for removing product images from galleries. - Implement UpdateProductsCommand and handler for updating product details and images. - Create GetProductGalleryQuery and handler for retrieving product galleries. - Implement PermissionService for role-based access control using JWT claims. - Implement FileManagementService for handling file uploads and image optimization. - Define gRPC service and messages for file management in fms.proto. - Add FluentValidation for request validation in various commands. - Create PermissionInterceptor for enforcing permissions on gRPC methods.
This commit is contained in:
@@ -64,6 +64,8 @@
|
||||
<Protobuf Include="Protos\appversion.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<!-- Inventory Management System -->
|
||||
<Protobuf Include="Protos\inventory.proto" ProtoRoot="Protos\" GrpcServices="Both" />
|
||||
<!-- FMS (File Management Service) - gRPC Client only -->
|
||||
<Protobuf Include="Protos\fms.proto" ProtoRoot="Protos\" GrpcServices="Client" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PushToFoursatNuget" AfterTargets="Pack" Condition="'$(CI)' != 'true'">
|
||||
|
||||
@@ -87,6 +87,8 @@ message CreateDiscountProductRequest
|
||||
int32 sort_order = 9;
|
||||
bool is_active = 10;
|
||||
repeated int64 category_ids = 11;
|
||||
ImageFileModel image_file = 12;
|
||||
ImageFileModel thumbnail_file = 13;
|
||||
}
|
||||
|
||||
message CreateDiscountProductResponse
|
||||
@@ -108,6 +110,8 @@ message UpdateDiscountProductRequest
|
||||
int32 sort_order = 9;
|
||||
bool is_active = 10;
|
||||
repeated int64 category_ids = 11;
|
||||
ImageFileModel image_file = 12;
|
||||
ImageFileModel thumbnail_file = 13;
|
||||
}
|
||||
|
||||
// Delete Product
|
||||
@@ -246,3 +250,11 @@ message DiscountProductImageDto
|
||||
int32 sort_order = 7;
|
||||
bool is_active = 8;
|
||||
}
|
||||
|
||||
// File upload model for binary image uploads from BackOffice
|
||||
message ImageFileModel
|
||||
{
|
||||
bytes file = 1;
|
||||
string mime = 2;
|
||||
string file_name = 3;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package fms;
|
||||
|
||||
import "google/protobuf/wrappers.proto";
|
||||
|
||||
option csharp_namespace = "CMSMicroservice.Protobuf.Protos.FMS";
|
||||
|
||||
service FileInfoContract
|
||||
{
|
||||
rpc CreateNewFileInfo(CreateNewFileInfoRequest) returns (CreateNewFileInfoResponse);
|
||||
rpc DeleteFileInfo(DeleteFileInfoRequest) returns (DeleteFileInfoResponse);
|
||||
}
|
||||
|
||||
message CreateNewFileInfoRequest
|
||||
{
|
||||
string directory = 1;
|
||||
bytes file = 2;
|
||||
string mime = 3;
|
||||
bool is_base64 = 4;
|
||||
google.protobuf.StringValue file_name = 5;
|
||||
}
|
||||
|
||||
message CreateNewFileInfoResponse
|
||||
{
|
||||
int64 id = 1;
|
||||
string file = 2;
|
||||
}
|
||||
|
||||
message DeleteFileInfoRequest
|
||||
{
|
||||
int64 id = 1;
|
||||
}
|
||||
|
||||
message DeleteFileInfoResponse
|
||||
{
|
||||
bool success = 1;
|
||||
}
|
||||
@@ -270,6 +270,8 @@ message InventoryItemDto {
|
||||
string product_title = 15;
|
||||
int64 product_price = 16;
|
||||
google.protobuf.Timestamp created = 17;
|
||||
// آیا موجودی کم است (موجودی قابل فروش کمتر از حد هشدار)
|
||||
bool is_low_stock = 18;
|
||||
}
|
||||
|
||||
message GetInventoryItemRequest {
|
||||
|
||||
@@ -78,6 +78,7 @@ message CreateManualPaymentRequest
|
||||
google.protobuf.StringValue reference_number = 5;
|
||||
google.protobuf.StringValue image_path = 6;
|
||||
google.protobuf.Int64Value image_document_id = 7;
|
||||
FileUploadModel image_file = 8;
|
||||
}
|
||||
|
||||
message CreateManualPaymentResponse
|
||||
@@ -155,3 +156,11 @@ message ProcessManualMembershipPaymentResponse
|
||||
string message = 4;
|
||||
}
|
||||
|
||||
// File upload model for binary image uploads from BackOffice
|
||||
message FileUploadModel
|
||||
{
|
||||
bytes file = 1;
|
||||
string file_name = 2;
|
||||
string mime = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ message CreateNewPackageRequest
|
||||
string description = 2;
|
||||
string image_path = 3;
|
||||
int64 price = 4;
|
||||
BoostCardFileModel image_file = 5;
|
||||
}
|
||||
message CreateNewPackageResponse
|
||||
{
|
||||
@@ -125,6 +126,7 @@ message UpdatePackageRequest
|
||||
string description = 3;
|
||||
string image_path = 4;
|
||||
int64 price = 5;
|
||||
BoostCardFileModel image_file = 6;
|
||||
}
|
||||
message DeletePackageRequest
|
||||
{
|
||||
@@ -412,3 +414,11 @@ enum PaymentStatusEnum
|
||||
PAYMENT_STATUS_FAILED = 2;
|
||||
PAYMENT_STATUS_REFUNDED = 3;
|
||||
}
|
||||
|
||||
// File upload model for binary image uploads from BackOffice
|
||||
message BoostCardFileModel
|
||||
{
|
||||
bytes file = 1;
|
||||
string file_name = 2;
|
||||
string mime = 3;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,50 @@ service ProductsContract
|
||||
get: "/Customer/GetProducts"
|
||||
};
|
||||
};
|
||||
|
||||
// ============= Category-Product DragDrop Methods =============
|
||||
|
||||
rpc GetProductsForCategory(GetProductsForCategoryRequest) returns (GetProductsForCategoryResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetProductsForCategory"
|
||||
};
|
||||
};
|
||||
rpc GetCategories(GetCategoriesRequest) returns (GetCategoriesResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetCategories"
|
||||
};
|
||||
};
|
||||
rpc UpdateProductCategories(UpdateProductCategoriesRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
post: "/UpdateProductCategories"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc UpdateCategoryProducts(UpdateCategoryProductsRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
post: "/UpdateCategoryProducts"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
|
||||
// ============= Product Image Management =============
|
||||
|
||||
rpc AddProductImage(AddProductImageRequest) returns (AddProductImageResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/AddProductImage"
|
||||
body: "*"
|
||||
};
|
||||
};
|
||||
rpc RemoveProductImage(RemoveProductImageRequest) returns (google.protobuf.Empty){
|
||||
option (google.api.http) = {
|
||||
delete: "/RemoveProductImage"
|
||||
};
|
||||
};
|
||||
rpc GetProductGallery(GetProductGalleryRequest) returns (GetProductGalleryResponse){
|
||||
option (google.api.http) = {
|
||||
get: "/GetProductGallery"
|
||||
};
|
||||
};
|
||||
}
|
||||
message CreateNewProductsRequest
|
||||
{
|
||||
@@ -96,6 +140,10 @@ message CreateNewProductsRequest
|
||||
int32 remaining_count = 12;
|
||||
// لیست شناسه دستهبندیهای محصول
|
||||
repeated int64 category_ids = 13;
|
||||
// فایل تصویر اصلی محصول (آپلود باینری)
|
||||
ImageFileModel image_file = 14;
|
||||
// فایل تصویر بندانگشتی محصول (آپلود باینری)
|
||||
ImageFileModel thumbnail_file = 15;
|
||||
}
|
||||
message CreateNewProductsResponse
|
||||
{
|
||||
@@ -118,6 +166,10 @@ message UpdateProductsRequest
|
||||
int32 remaining_count = 13;
|
||||
// لیست شناسه دستهبندیهای محصول
|
||||
repeated int64 category_ids = 14;
|
||||
// فایل تصویر اصلی محصول (آپلود باینری)
|
||||
ImageFileModel image_file = 15;
|
||||
// فایل تصویر بندانگشتی محصول (آپلود باینری)
|
||||
ImageFileModel thumbnail_file = 16;
|
||||
}
|
||||
message DeleteProductsRequest
|
||||
{
|
||||
@@ -334,3 +386,97 @@ message ToggleProductStatusResponse
|
||||
int32 failed = 3;
|
||||
repeated BulkOperationError errors = 4;
|
||||
}
|
||||
|
||||
// Category Product Item (for drag-drop UI)
|
||||
message CategoryProductItem
|
||||
{
|
||||
int64 id = 1;
|
||||
string title = 2;
|
||||
bool selected = 3;
|
||||
}
|
||||
|
||||
// Get Products for Category
|
||||
message GetProductsForCategoryRequest
|
||||
{
|
||||
int64 category_id = 1;
|
||||
}
|
||||
|
||||
message GetProductsForCategoryResponse
|
||||
{
|
||||
repeated CategoryProductItem items = 1;
|
||||
}
|
||||
|
||||
// Category Item (for product categories drag-drop)
|
||||
message CategoryItem
|
||||
{
|
||||
int64 id = 1;
|
||||
string title = 2;
|
||||
bool selected = 3;
|
||||
}
|
||||
|
||||
// Get Categories for Product
|
||||
message GetCategoriesRequest
|
||||
{
|
||||
int64 product_id = 1;
|
||||
}
|
||||
|
||||
message GetCategoriesResponse
|
||||
{
|
||||
repeated CategoryItem items = 1;
|
||||
}
|
||||
|
||||
// Update Product Categories
|
||||
message UpdateProductCategoriesRequest
|
||||
{
|
||||
int64 product_id = 1;
|
||||
repeated int64 category_ids = 2;
|
||||
}
|
||||
|
||||
// Update Category Products
|
||||
message UpdateCategoryProductsRequest
|
||||
{
|
||||
int64 category_id = 1;
|
||||
repeated int64 product_ids = 2;
|
||||
}
|
||||
|
||||
// Image File Model
|
||||
message ImageFileModel
|
||||
{
|
||||
bytes file = 1;
|
||||
string mime = 2;
|
||||
string file_name = 3;
|
||||
}
|
||||
|
||||
// Get Product Gallery
|
||||
message GetProductGalleryRequest
|
||||
{
|
||||
int64 product_id = 1;
|
||||
}
|
||||
|
||||
message GetProductGalleryResponse
|
||||
{
|
||||
repeated ProductGalleryItem items = 1;
|
||||
}
|
||||
|
||||
// Add Product Image
|
||||
message AddProductImageRequest
|
||||
{
|
||||
int64 product_id = 1;
|
||||
string title = 2;
|
||||
ImageFileModel image_file = 3;
|
||||
}
|
||||
|
||||
message AddProductImageResponse
|
||||
{
|
||||
int64 product_gallery_id = 1;
|
||||
int64 product_image_id = 2;
|
||||
string title = 3;
|
||||
string image_path = 4;
|
||||
string image_thumbnail_path = 5;
|
||||
}
|
||||
|
||||
// Remove Product Image
|
||||
message RemoveProductImageRequest
|
||||
{
|
||||
int64 product_gallery_id = 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using FluentValidation;
|
||||
using CMSMicroservice.Protobuf.Protos.User;
|
||||
namespace CMSMicroservice.Protobuf.Validator.User;
|
||||
|
||||
public class VerifyOtpTokenRequestValidator : AbstractValidator<VerifyOtpTokenRequest>
|
||||
{
|
||||
public VerifyOtpTokenRequestValidator()
|
||||
{
|
||||
RuleFor(model => model.Mobile)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Purpose)
|
||||
.NotEmpty();
|
||||
RuleFor(model => model.Code)
|
||||
.NotEmpty();
|
||||
}
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(ValidationContext<VerifyOtpTokenRequest>.CreateWithOptions((VerifyOtpTokenRequest)model, x => x.IncludeProperties(propertyName)));
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user