Skip to content

Commit

Permalink
feat: @CCT-26 CRUD Profile (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
leesoonduck3009 authored Oct 24, 2024
1 parent cc69afd commit a3d75be
Show file tree
Hide file tree
Showing 36 changed files with 1,515 additions and 89 deletions.
1 change: 1 addition & 0 deletions src/ChitChat.Application/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static IServiceCollection AddServices(this IServiceCollection services)
{
services.AddScoped<IUserService, UserService>();
services.AddScoped<IConversationService, ConversationService>();
services.AddScoped<IProfileService, ProfileService>();
return services;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/ChitChat.Application/Exceptions/ForbiddenException.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using ChitChat.Application.Models;
using ChitChat.Application.Models;

namespace ChitChat.Application.Exceptions
{
public class ForbiddenException : ApplicationException
{
public ForbiddenException(string message) : base(message)
public ForbiddenException(string message) : base(message)
{
Code = ApiResultErrorCodes.Forbidden;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ChitChat.Application/Exceptions/InvalidModelException.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ChitChat.Application.Models;
using ChitChat.Application.Models;

namespace ChitChat.Application.Exceptions;

Expand All @@ -10,4 +10,4 @@ public InvalidModelException(string message, bool transactionRollback = true) :
Code = ApiResultErrorCodes.ModelValidation;
TransactionRollback = transactionRollback;
}
}
}
5 changes: 3 additions & 2 deletions src/ChitChat.Application/Localization/ValidationText.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ChitChat.Domain.Common;
using ChitChat.Domain.Common;

namespace ChitChat.Application.Localization
{
Expand All @@ -7,6 +7,7 @@ public static class ValidationTexts
public static readonly LocalizedText NotFound = LocalizedText.New("{0} {1} not found").AddDefaultText("{0} {1} không tồn tại.");
public static readonly LocalizedText Conflict = LocalizedText.New("{0} {1} is already existed").AddDefaultText("{0} với {1} đã tồn tại.");
public static readonly LocalizedText NotValidate = LocalizedText.New("{0} {1} is not validated").AddDefaultText("{0} với {1} không hợp lệ.");

public static readonly LocalizedText Forbidden = LocalizedText.New("Access to {0} {1} is forbidden").AddDefaultText("Quyền truy cập {0} với {1} bị cấm.");
public static readonly LocalizedText Unauthorized = LocalizedText.New("Unauthorized access to {0} {1}").AddDefaultText("Truy cập trái phép {0} với {1}.");
}
}
10 changes: 10 additions & 0 deletions src/ChitChat.Application/Mapping/UserProfile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoMapper;

using ChitChat.Application.Models.Dtos.User;
using ChitChat.Domain.Identity;

Expand All @@ -11,6 +12,15 @@ public UserProfile()
CreateMap<UserApplication, UserDto>();
CreateMap<RegisterationRequestDto, UserApplication>();
CreateMap<LoginRequestDto, UserApplication>();
CreateMap<UserApplication, ProfileDto>();
CreateMap<ProfileDto, ChitChat.Domain.Entities.UserEntities.Profile>();
CreateMap<ProfileRequestDto, ChitChat.Domain.Entities.UserEntities.Profile>();
CreateMap<ChitChat.Domain.Entities.UserEntities.Profile, ProfileDto>()
.ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.UserApplication != null ? src.UserApplication.DisplayName : string.Empty))
.ForMember(dest => dest.AvatarUrl, opt => opt.MapFrom(src => src.UserApplication != null ? src.UserApplication.AvatarUrl : string.Empty))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.UserApplication != null ? src.UserApplication.Email : string.Empty))
.ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom(src => src.UserApplication != null ? src.UserApplication.PhoneNumber : string.Empty));
CreateMap<ProfileRequestDto, UserApplication>();
}
}
}
14 changes: 14 additions & 0 deletions src/ChitChat.Application/Models/Dtos/User/ProfileDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace ChitChat.Application.Models.Dtos.User
{
public class ProfileDto
{
public Guid Id { get; set; }
public string DisplayName { get; set; }
public string AvatarUrl { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string? Bio { get; set; }
public DateTime DateOfBirth { get; set; }
public string? Gender { get; set; }
}
}
12 changes: 12 additions & 0 deletions src/ChitChat.Application/Models/Dtos/User/ProfileRequestDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace ChitChat.Application.Models.Dtos.User
{
public class ProfileRequestDto
{
public string DisplayName { get; set; }
public string AvatarUrl { get; set; }
public string PhoneNumber { get; set; }
public string? Bio { get; set; }
public DateTime DateOfBirth { get; set; }
public string? Gender { get; set; }
}
}
5 changes: 2 additions & 3 deletions src/ChitChat.Application/Models/Dtos/User/UserDto.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace ChitChat.Application.Models.Dtos.User
namespace ChitChat.Application.Models.Dtos.User
{
public class UserDto
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName { get; set; }
public string AvatarUrl { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
Expand Down
13 changes: 13 additions & 0 deletions src/ChitChat.Application/Services/Interface/IProfileService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using ChitChat.Application.Models.Dtos.User;

namespace ChitChat.Application.Services.Interface
{
public interface IProfileService
{
Task<List<ProfileDto>> GetAllProfilesAsync(string searchText, int pageIndex, int pageSize);
Task<ProfileDto> GetProfileByIdAsync(Guid userId);
Task<ProfileDto> CreatProfileAsync(ProfileRequestDto request);
Task<ProfileDto> UpdateProfileAsync(Guid userId, ProfileDto request);

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ChitChat.Application.Models.Dtos.User;
using ChitChat.Application.Models.Dtos.User;

namespace ChitChat.Application.Services.Interface
{
Expand Down
90 changes: 90 additions & 0 deletions src/ChitChat.Application/Services/ProfileService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using AutoMapper;

using ChitChat.Application.Exceptions;
using ChitChat.Application.Helpers;
using ChitChat.Application.Localization;
using ChitChat.Application.Models.Dtos.User;
using ChitChat.Application.Services.Interface;
using ChitChat.DataAccess.Repositories.Interface;
using ChitChat.DataAccess.Repositories.Interrface;

using Microsoft.EntityFrameworkCore;

namespace ChitChat.Application.Services
{
public class ProfileService : IProfileService
{
private readonly IMapper _mapper;
private readonly IBaseRepository<Domain.Entities.UserEntities.Profile> _profileRepository;
private readonly IClaimService _claimService;
private readonly IUserRepository _userRepository;
public ProfileService(IMapper mapper
, IBaseRepository<Domain.Entities.UserEntities.Profile> profileRepository
, IClaimService claimService
, IUserRepository userRepository)
{
this._mapper = mapper;
this._profileRepository = profileRepository;
this._claimService = claimService;
this._userRepository = userRepository;
}
public async Task<List<ProfileDto>> GetAllProfilesAsync(string searchText, int pageIndex, int pageSize)
{
var paginationResponse = await _profileRepository.GetAllAsync(
p => p.IsDeleted == false && p.SearchData.Contains(searchText), p => p.OrderByDescending(p => p.Id)
, pageIndex, pageSize, p => p.Include(p => p.UserApplication));
return _mapper.Map<List<ProfileDto>>(paginationResponse.Items);
}
public async Task<ProfileDto> GetProfileByIdAsync(Guid userId)
{
var userProfile = await _profileRepository.GetFirstOrDefaultAsync(p => p.Id == userId, p => p.Include(c => c.UserApplication));
if (userProfile == null)
{
throw new NotFoundException(ValidationTexts.NotFound.Format("User", userId));
}
var response = _mapper.Map<ProfileDto>(userProfile);
return response;
}
public async Task<ProfileDto> CreatProfileAsync(ProfileRequestDto request)
{
var userId = _claimService.GetUserId();
Domain.Entities.UserEntities.Profile userProfile = this._mapper.Map<Domain.Entities.UserEntities.Profile>(request);
userProfile.Id = Guid.Parse(userId);
userProfile.UserApplicationId = userId;
if (await _profileRepository.GetFirstOrDefaultAsync(p => p.Id == userProfile.Id) != null)
throw new ConflictException(ValidationTexts.Conflict.Format(userProfile.GetType(), userId));
var userApplication = await _userRepository.GetFirstOrDefaultAsync(p => p.Id == userId.ToString());
userProfile.SearchData = this.GenerateSearchData(request.DisplayName, userApplication.Email ?? "", userId);
await _profileRepository.AddAsync(userProfile);
_mapper.Map(request, userApplication);
await _userRepository.UpdateAsync(userApplication);
var response = _mapper.Map<ProfileDto>(userProfile);
_mapper.Map(userApplication, response);
return response;
}
public async Task<ProfileDto> UpdateProfileAsync(Guid userId, ProfileDto request)
{
if (userId.ToString() != _claimService.GetUserId())
{
throw new ForbiddenException(ValidationTexts.Forbidden.Format(typeof(Domain.Entities.UserEntities.Profile), userId));
}
var userProfile = await _profileRepository.GetFirstOrDefaultAsync(p => p.Id == userId);
var userApplication = await _userRepository.GetFirstOrDefaultAsync(p => p.Id == userId.ToString());
if (userProfile == null || userApplication == null)
{
throw new NotFoundException(ValidationTexts.NotFound.Format(userProfile.GetType(), userId));
}
_mapper.Map(request, userProfile);
_mapper.Map(request, userApplication);
userProfile.SearchData = this.GenerateSearchData(request.DisplayName, userApplication.Email ?? "", userId.ToString());
await _profileRepository.UpdateAsync(userProfile);
await _userRepository.UpdateAsync(userApplication);
return request;
}
private string GenerateSearchData(string displayName, string email, string userId)
{
return displayName + email + userId;

}
}
}
14 changes: 6 additions & 8 deletions src/ChitChat.Application/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoMapper;
using AutoMapper;

using ChitChat.Application.Exceptions;
using ChitChat.Application.Helpers;
using ChitChat.Application.Localization;
Expand All @@ -8,6 +9,7 @@
using ChitChat.DataAccess.Repositories.Interrface;
using ChitChat.Domain.Entities;
using ChitChat.Domain.Identity;

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -98,14 +100,11 @@ public async Task<bool> RegisterAsync(RegisterationRequestDto registerationReque
{
UserName = registerationRequestDto.Email,
Email = registerationRequestDto.Email,
FirstName = registerationRequestDto.FirstName,
LastName = registerationRequestDto.LastName,
DisplayName = registerationRequestDto.LastName + registerationRequestDto.FirstName,
PhoneNumber = registerationRequestDto.PhoneNumber,
NormalizedEmail = registerationRequestDto.Email.ToUpper(),
AvatarUrl = "",
Bio = "",
UserStatus = Domain.Enums.UserStatus.Public,
Gender = "Male"
};
var result = await _userManager.CreateAsync(newUser, registerationRequestDto.Password); // Hash password by .net identity
if (result.Succeeded)
Expand All @@ -115,9 +114,8 @@ public async Task<bool> RegisterAsync(RegisterationRequestDto registerationReque
{
Email = userToReturn.Email,
Id = userToReturn.Id,
FirstName = userToReturn.FirstName,
LastName = userToReturn.FirstName,
PhoneNumber = userToReturn.LastName,
DisplayName = userToReturn.DisplayName,
PhoneNumber = userToReturn.PhoneNumber ?? "",
};
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using ChitChat.Application.Models.Dtos.User;
using ChitChat.Domain.Enums;

using FluentValidation;

namespace ChitChat.Application.Validators.User
{
public class ProfileRequestValidator : AbstractValidator<ProfileRequestDto>
{
public ProfileRequestValidator()
{
RuleFor(profile => profile.Gender)
.NotNull()
.NotEmpty()
.Must(gender => Enum.TryParse(typeof(Gender), gender, true, out _))
.WithMessage("Invalid gender value. Allowed values are: Male, Female, Other.");
RuleFor(profile => profile.Bio)
.NotNull()
.NotEmpty();
RuleFor(profile => profile.DateOfBirth)
.NotEmpty()
.NotNull();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ChitChat.Application.Models.Dtos.User;
using ChitChat.Application.Models.Dtos.User;

using FluentValidation;
namespace ChitChat.Application.Validators.User
{
Expand Down
19 changes: 19 additions & 0 deletions src/ChitChat.DataAccess/Configurations/ProfileConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using ChitChat.Domain.Entities.UserEntities;

using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ChitChat.DataAccess.Configurations
{
public class ProfileConfiguration : IEntityTypeConfiguration<Profile>
{
public ProfileConfiguration() { }

public void Configure(EntityTypeBuilder<Profile> builder)
{
builder
.HasOne(p => p.UserApplication)
.WithOne()
.HasForeignKey<Profile>(p => p.UserApplicationId);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using ChitChat.Domain.Entities.UserEntities;

using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ChitChat.DataAccess.Configurations
{
public class UserFollowerRequestConfiguration: IEntityTypeConfiguration<UserFollowerRequest>
public class UserFollowerRequestConfiguration : IEntityTypeConfiguration<UserFollowerRequest>
{
public void Configure(EntityTypeBuilder<UserFollowerRequest> modelBuilder)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ChitChat.Domain.Entities.SystemEntities;

using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ChitChat.DataAccess.Configurations
Expand Down
7 changes: 5 additions & 2 deletions src/ChitChat.DataAccess/Data/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using ChitChat.Domain.Entities;
using System.Reflection;

using ChitChat.Domain.Entities;
using ChitChat.Domain.Entities.ChatEntities;
using ChitChat.Domain.Entities.PostEntities;
using ChitChat.Domain.Entities.PostEntities.Reaction;
using ChitChat.Domain.Entities.SystemEntities;
using ChitChat.Domain.Entities.UserEntities;
using ChitChat.Domain.Identity;

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using System.Reflection;

namespace ChitChat.DataAccess.Data
{
Expand All @@ -27,6 +29,7 @@ public class ApplicationDbContext : IdentityDbContext<UserApplication, Applicati
public DbSet<ReactionComment> ReactionComments { get; set; }
public DbSet<UserInteraction> UserInteractions { get; set; }
public DbSet<Notification> Notifications { get; set; }
public DbSet<Profile> Profiles { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{

Expand Down
10 changes: 2 additions & 8 deletions src/ChitChat.DataAccess/Data/DbContextSeed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,21 @@ public static async Task SeedDatabaseAsync(UserManager<UserApplication> userMana
{
Id = Guid.NewGuid().ToString(),
UserName = "admin",
FirstName = "admin",
LastName = "1",
DisplayName = "admin",
Email = "admin@gmail.com",
EmailConfirmed = true,
AvatarUrl = "",
Bio = "",
UserStatus = Domain.Enums.UserStatus.Public,
Gender = "Male"
},
new UserApplication
{
Id = Guid.NewGuid().ToString(),
UserName = "admin1",
FirstName = "admin1",
LastName = "2",
DisplayName = "admin1",
Email = "admin1@gmail.com",
EmailConfirmed = true,
AvatarUrl = "",
Bio = "",
UserStatus = Domain.Enums.UserStatus.Public,
Gender = "Male"
}
};

Expand Down
Loading

0 comments on commit a3d75be

Please sign in to comment.