feat: Implement file management and authorization features
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:
masoodafar-web
2026-02-10 22:04:54 +03:30
parent f64b6be7da
commit b42d9e141d
65 changed files with 3082 additions and 2384 deletions
@@ -16,7 +16,10 @@ using CMSMicroservice.Application.UserCQ.Commands.AcceptContract;
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
using CMSMicroservice.Application.Common.Interfaces;
using Google.Protobuf.WellKnownTypes;
using Microsoft.EntityFrameworkCore;
using Grpc.Core;
using System.Collections.Generic;
using System.Linq;
using MediatR;
@@ -28,11 +31,25 @@ public class UserService : UserContract.UserContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
private readonly ISender _sender;
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUserService;
private readonly IHashService _hashService;
private readonly IFileManagementService _fileManagementService;
public UserService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender)
public UserService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender,
IApplicationDbContext context,
ICurrentUserService currentUserService,
IHashService hashService,
IFileManagementService fileManagementService)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
_context = context;
_currentUserService = currentUserService;
_hashService = hashService;
_fileManagementService = fileManagementService;
}
public override async Task<CreateNewUserResponse> CreateNewUser(CreateNewUserRequest request, ServerCallContext context)
{
@@ -90,33 +107,58 @@ public class UserService : UserContract.UserContractBase
public override async Task<GetUserForCustomerResponse> GetUserForCustomer(GetUserForCustomerRequest request, ServerCallContext context)
{
// Mock implementation for Customer Get User
await Task.Delay(10); // Simulate async operation
var userId = GetCurrentUserId();
var user = await _context.Users
.AsNoTracking()
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
return new GetUserForCustomerResponse
{
Id = 123,
FirstName = "احمد",
LastName = "محمدی",
Mobile = "09123456789",
Email = "ahmad.mohammadi@example.com",
NationalCode = "1234567890",
AvatarPath = "/avatars/user_123.jpg",
ParentId = 100,
ReferralCode = "REF123456",
IsMobileVerified = true,
MobileVerifiedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(new DateTime(2024, 1, 15), DateTimeKind.Utc)),
EmailNotifications = true,
SmsNotifications = true,
PushNotifications = false,
BirthDate = Timestamp.FromDateTime(DateTime.SpecifyKind(new DateTime(1990, 5, 20), DateTimeKind.Utc))
Id = user.Id,
FirstName = user.FirstName ?? string.Empty,
LastName = user.LastName ?? string.Empty,
Mobile = user.Mobile,
Email = user.Email ?? string.Empty,
NationalCode = user.NationalCode ?? string.Empty,
AvatarPath = user.AvatarPath ?? string.Empty,
ParentId = user.NetworkParentId,
ReferralCode = user.ReferralCode ?? string.Empty,
IsMobileVerified = user.IsMobileVerified,
MobileVerifiedAt = user.MobileVerifiedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.MobileVerifiedAt.Value, DateTimeKind.Utc))
: null,
EmailNotifications = user.EmailNotifications,
SmsNotifications = user.SmsNotifications,
PushNotifications = user.PushNotifications,
BirthDate = user.BirthDate.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc))
: null
};
}
public override async Task<Empty> UpdateCustomerProfile(UpdateCustomerProfileRequest request, ServerCallContext context)
{
// Mock implementation for Update Customer Profile
await Task.Delay(10);
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
if (request.FirstName != null) user.FirstName = request.FirstName;
if (request.LastName != null) user.LastName = request.LastName;
if (request.Email != null) user.Email = request.Email;
if (request.NationalCode != null) user.NationalCode = request.NationalCode;
if (request.BirthDate != null) user.BirthDate = request.BirthDate.ToDateTime();
await _context.SaveChangesAsync(context.CancellationToken);
return new Empty();
}
@@ -153,9 +195,6 @@ public class UserService : UserContract.UserContractBase
public override async Task<ChangeCustomerPasswordResponse> ChangeCustomerPassword(ChangeCustomerPasswordRequest request, ServerCallContext context)
{
// Mock implementation for Change Customer Password
await Task.Delay(10);
if (request.NewPassword != request.ConfirmPassword)
{
return new ChangeCustomerPasswordResponse
@@ -174,6 +213,32 @@ public class UserService : UserContract.UserContractBase
};
}
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
// Verify current password
if (!string.IsNullOrEmpty(user.HashPassword))
{
if (!_hashService.VerifyPassword(request.CurrentPassword, user.HashPassword))
{
return new ChangeCustomerPasswordResponse
{
Success = false,
Message = "رمز عبور فعلی نادرست است"
};
}
}
// Hash and save new password
user.HashPassword = _hashService.HashPassword(request.NewPassword);
await _context.SaveChangesAsync(context.CancellationToken);
return new ChangeCustomerPasswordResponse
{
Success = true,
@@ -233,9 +298,6 @@ public class UserService : UserContract.UserContractBase
public override async Task<UploadCustomerAvatarResponse> UploadCustomerAvatar(UploadCustomerAvatarRequest request, ServerCallContext context)
{
// Mock implementation for Upload Customer Avatar
await Task.Delay(10);
if (request.FileData == null || request.FileData.Length == 0)
{
return new UploadCustomerAvatarResponse
@@ -264,10 +326,36 @@ public class UserService : UserContract.UserContractBase
};
}
// Simulate file upload and generate URL
var fileName = $"avatar_{DateTime.Now.Ticks}.{request.FileMimeType?.Split('/').LastOrDefault()}";
var avatarUrl = $"/uploads/avatars/{fileName}";
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
// Upload to FMS
var fileBytes = request.FileData.ToByteArray();
var fileName = $"avatar_{userId}_{DateTime.UtcNow.Ticks}";
var mime = request.FileMimeType ?? "image/jpeg";
var avatarUrl = await _fileManagementService.UploadFileAsync(
"Avatars", fileBytes, mime, fileName, context.CancellationToken);
if (string.IsNullOrEmpty(avatarUrl))
{
return new UploadCustomerAvatarResponse
{
Success = false,
Message = "خطا در آپلود فایل. لطفاً مجدد تلاش کنید"
};
}
// Update user avatar path in DB
user.AvatarPath = avatarUrl;
await _context.SaveChangesAsync(context.CancellationToken);
return new UploadCustomerAvatarResponse
{
Success = true,
@@ -295,8 +383,32 @@ public class UserService : UserContract.UserContractBase
public override async Task<Empty> UpdateCustomerSettings(UpdateCustomerSettingsRequest request, ServerCallContext context)
{
// Mock implementation for Update Customer Settings
await Task.Delay(10);
var userId = GetCurrentUserId();
var user = await _context.Users
.Where(u => u.Id == userId && !u.IsDeleted)
.FirstOrDefaultAsync(context.CancellationToken);
if (user == null)
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
user.EmailNotifications = request.EmailNotifications;
user.SmsNotifications = request.SmsNotifications;
user.PushNotifications = request.PushNotifications;
// MarketingNotifications, PreferredLanguage, TimeZone, TwoFactorAuth
// are stored at application level if needed in future
await _context.SaveChangesAsync(context.CancellationToken);
return new Empty();
}
// ============= Helper Methods =============
private long GetCurrentUserId()
{
if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0)
return userId;
throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید"));
}
}