| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Authorization;
- using Microsoft.AspNetCore.Authentication.JwtBearer;
- using Microsoft.EntityFrameworkCore;
- using bitforum.DTOs.Request;
- using bitforum.DTOs.Response;
- using bitforum.Repository;
- using bitforum.Services;
- using bitforum.Helpers;
- using bitforum.Constants;
- using bitforum.Models.Log;
- using bitforum.Extensions;
- namespace bitforum.Controllers.API
- {
- [ApiController]
- [Route("api/[controller]")]
- [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] // JWT 인증 적용
- public class AccountController: ControllerBase
- {
- private readonly ILogger<AccountController> _logger;
- private readonly DefaultDbContext _db;
- private readonly IMemberRepository _memberRepository;
- private readonly IMailService _mailService;
- private readonly IConfigService _configService;
- private ResultDto _result = new ResultDto();
- public AccountController(ILogger<AccountController> logger, DefaultDbContext db, IMemberRepository memberRepository, IMailService mailService, IConfigService configService)
- {
- _logger = logger;
- _db = db;
- _memberRepository = memberRepository;
- _mailService = mailService;
- _configService = configService;
- }
- // 회원 조회
- [HttpGet("Info/{email}")]
- public async Task<ActionResult<ResultDto>> Info([FromRoute] string email)
- {
- try
- {
- if (string.IsNullOrEmpty(email))
- {
- throw new Exception("이메일을 입력해주세요.");
- }
- var member = await _memberRepository.FindMemberByEmail(email);
- if (member is null)
- {
- throw new Exception("회원 정보를 찾을 수 없습니다.");
- }
- _result.Data = member;
- }
- catch (Exception e)
- {
- _logger.LogError(e, e.Message);
- _result.Ok = false;
- _result.Status = StatusCodes.Status400BadRequest;
- _result.Message = e.Message;
- }
- return _result;
- }
- // 별명 수정
- [HttpPost("change-nickname")]
- public async Task<ActionResult<ResultDto>> ChangeNickname([FromBody] ChangeNicknameDto request)
- {
- try
- {
- if (!ModelState.IsValid)
- {
- _result.Errors = ModelState.GetErrors();
- throw new Exception("유효성 검사에 실패했습니다.");
- }
- // 중복 여부 확인
- var isExist = await _memberRepository.IsExistNickname(request.Name);
- if (isExist)
- {
- throw new Exception("이미 사용중인 별명입니다.");
- }
- // 회원 조회
- var member = await _memberRepository.FindMemberByID(request.ID);
- if (member is null)
- {
- throw new Exception("회원 정보를 찾을 수 없습니다.");
- }
- // 별명 변경 가능 기간 계산 (현재 날짜 - 설정된 변경 기간)
- var changeNicknameDay = Config.Register.ChangeNicknameDay ?? 0;
- if (changeNicknameDay > 0)
- {
- // 사용자의 마지막 별명 변경 날짜가 설정된 제한일 이내인지 확인
- if (member.LastNameChangedAt.HasValue)
- {
- int daysSinceLastChange = (int)(DateTime.UtcNow - member.LastNameChangedAt.Value).TotalDays; // 남은 일수 계산
- if (daysSinceLastChange < changeNicknameDay)
- {
- throw new Exception($"별명 변경은 {changeNicknameDay - daysSinceLastChange}일 후에 가능합니다.");
- }
- }
- }
- await _db.NameChangeLog.AddAsync(new NameChangeLog
- {
- MemberID = member.ID,
- BeforeName = member.Name,
- AfterName = request.Name,
- CreatedAt = DateTime.UtcNow
- });
- member.Name = request.Name;
- member.LastNameChangedAt = DateTime.UtcNow;
- await _db.SaveChangesAsync();
- _result.Message = "별명이 변경되었습니다.";
- }
- catch (Exception e)
- {
- _logger.LogError(e, e.Message);
- _result.Ok = false;
- _result.Status = StatusCodes.Status400BadRequest;
- _result.Message = e.Message;
- }
- return _result;
- }
- // 이메일 변경
- [HttpPost("change-email")]
- public async Task<ActionResult<ResultDto>> ChangeEmail([FromBody] ChangeEmailDto request)
- {
- try
- {
- if (!ModelState.IsValid)
- {
- _result.Errors = ModelState.GetErrors();
- throw new Exception("유효성 검사에 실패했습니다.");
- }
- // 중복 여부 확인
- var isExist = await _memberRepository.IsExistEmail(request.Email);
- if (isExist)
- {
- throw new Exception("이미 사용중인 이메일입니다.");
- }
- // 회원 조회
- var member = await _memberRepository.FindMemberByID(request.ID);
- if (member is null)
- {
- throw new Exception("회원 정보를 찾을 수 없습니다.");
- }
- // 이메일 변경 가능 기간 계산 (현재 날짜 - 설정된 변경 기간)
- var changeEmailDay = Config.Register.ChangeEmailDay ?? 0;
- if (changeEmailDay > 0)
- {
- // 사용자의 마지막 이메일 변경 날짜가 설정된 제한일 이내인지 확인
- if (member.LastEmailChangedAt.HasValue)
- {
- int daysSinceLastChange = (int)(DateTime.UtcNow - member.LastEmailChangedAt.Value).TotalDays; // 남은 일수 계산
- if (daysSinceLastChange < changeEmailDay)
- {
- throw new Exception($"별명 변경은 {changeEmailDay - daysSinceLastChange}일 후에 가능합니다.");
- }
- }
- }
- // 이메일 인증 메일 발송
- await _mailService.SendEmailVerifyAsync(request.Email, member, new AdditionalData
- {
- Email = member.Email
- });
- _result.Message = "이메일 확인을 위해 인증 메일이 전송되었습니다.";
- }
- catch (Exception e)
- {
- _logger.LogError(e, e.Message);
- _result.Ok = false;
- _result.Status = StatusCodes.Status400BadRequest;
- _result.Message = e.Message;
- }
- return _result;
- }
- // 비밀번호 변경
- [HttpPost("change-password")]
- public async Task<ActionResult<ResultDto>> ChangePassword([FromBody] ChangePasswordDto request)
- {
- try
- {
- if (!ModelState.IsValid)
- {
- _result.Errors = ModelState.GetErrors();
- throw new Exception("유효성 검사에 실패하였습니다.");
- }
- var memberID = User.GetID();
- // 회원 확인
- var isExist = await _memberRepository.IsExistID(memberID);
- if (!isExist)
- {
- throw new Exception("존재하지 않는 회원입니다.");
- }
- // 회원 조회
- var member = await _memberRepository.FindMemberByID(memberID);
- if (member is null)
- {
- throw new Exception("회원 정보를 찾을 수 없습니다.");
- }
- // 현재 비밀번호가 맞는지 확인
- var isValidPassword = BCrypt.Net.BCrypt.Verify(request.CurrentPassword, member.Password);
- if (!isValidPassword)
- {
- throw new Exception("현재 비밀번호가 일치하지 않습니다.");
- }
- var newPassword = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
- if (member.Password == newPassword)
- {
- throw new Exception("기존 비밀번호와 동일합니다.");
- }
- var isValidPolicy = _configService.IsPasswordPolicyValid(request.ConfirmPassword);
- if (!isValidPolicy)
- {
- string message = "";
- int minLengthConfig = Config.Register.PasswordMinLength ?? 6;
- int uppercaseLengthConfig = Config.Register.PasswordUppercaseLength ?? 0;
- int numbersLengthConfig = Config.Register.PasswordNumbersLength ?? 0;
- int specialCharsLengthConfig = Config.Register.PasswordSpecialcharsLength ?? 0;
- // 위에 데이터로 안내 문구 만들어줘
- if (minLengthConfig > 0)
- {
- message += $"최소 {minLengthConfig}자 이상, ";
- }
- if (uppercaseLengthConfig > 0)
- {
- message += $"대문자 {uppercaseLengthConfig}자 이상, ";
- }
- if (numbersLengthConfig > 0)
- {
- message += $"숫자 {numbersLengthConfig}자 이상, ";
- }
- if (specialCharsLengthConfig > 0)
- {
- message += $"특수문자 {specialCharsLengthConfig}자 이상, ";
- }
- message = message.TrimEnd(',', ' ');
- throw new Exception($"입력하신 비밀번호는 사용하실 수 없습니다. 다음 조건을 충족해주세요.\n\n{message}");
- }
- member.Password = newPassword;
- member.PasswordUpdatedAt = DateTime.UtcNow;
- await _db.SaveChangesAsync();
- await _mailService.SendChangedPasswordEmailAsync(member);
- _result.Message = "비밀번호가 변경되었습니다.";
- }
- catch (Exception e)
- {
- _logger.LogError(e, e.Message);
- _result.Ok = false;
- _result.Status = StatusCodes.Status400BadRequest;
- _result.Message = e.Message;
- }
- return _result;
- }
- // 회원탈퇴
- [HttpPost("withdraw")]
- public async Task<ActionResult<ResultDto>> Withdraw()
- {
- try
- {
- var memberID = User.GetID();
- // 회원 확인
- var isExist = await _memberRepository.IsExistID(memberID);
- if (!isExist)
- {
- throw new Exception("존재하지 않는 회원입니다.");
- }
- // 회원 조회
- var member = await _memberRepository.FindMemberByID(memberID);
- if (member is null)
- {
- throw new Exception("회원 정보를 찾을 수 없습니다.");
- }
- if (member.IsWithdraw)
- {
- throw new Exception("이미 탈퇴한 회원입니다.");
- }
- member.IsWithdraw = true;
- member.DeletedAt = DateTime.UtcNow;
- // 탈퇴 완료 메일 발송
- await _mailService.SendWithdrawEmailAsync(member);
- // 로그인 강제 로그아웃(쿠키 삭제)
- Response.Cookies.Delete("accessToken");
- Response.Cookies.Delete("refreshToken");
- _result.Message = "정상적으로 탈퇴되었습니다. 이용해 주셔서 감사합니다.";
- }
- catch (Exception e)
- {
- _logger.LogError(e, e.Message);
- _result.Ok = false;
- _result.Status = StatusCodes.Status400BadRequest;
- _result.Message = e.Message;
- }
- return _result;
- }
- // 로그인 기록
- [HttpGet("login-log")]
- public async Task<ActionResult<ResultDto>> LoginLog([FromQuery] int page = 1, [FromQuery] string type = LoginLogType.Today)
- {
- try
- {
- var memberID = User.GetID();
- // 회원 확인
- var isExist = await _memberRepository.IsExistID(memberID);
- if (!isExist)
- {
- throw new Exception("존재하지 않는 회원입니다.");
- }
-
- if (!LoginLogType.ValidTypes.Contains(type))
- {
- throw new ArgumentException("유효하지 않은 타입입니다.");
- }
- DateTime now = DateTime.UtcNow;
- DateTime startDate = now.Date;
- DateTime endOfDate = type switch
- {
- LoginLogType.Today => now.Date.AddDays(1).AddTicks(-1),
- LoginLogType.Week => now.AddDays(-7),
- LoginLogType.Month => now.AddMonths(-1),
- LoginLogType.HalfYear => now.AddMonths(-6),
- _ => DateTime.MinValue // 전체 조회
- };
- var query = _db.LoginLog.Where(c => c.MemberID == memberID && c.CreatedAt >= startDate && c.CreatedAt <= endOfDate)
- .Select(c => new
- {
- c.ID,
- c.MemberID,
- c.Success,
- c.IpAddress,
- c.UserAgent,
- CreatedAt = c.CreatedAt.ToLocalTime()
- })
- .OrderByDescending(c => c.ID)
- .Skip(page * 10)
- .Take(10);
- var logs = await query.ToListAsync();
- if (logs.Count == 0)
- {
- throw new Exception("로그인 기록이 없습니다.");
- }
- _result.Data = logs;
- }
- catch (Exception e)
- {
- _logger.LogError(e, e.Message);
- _result.Ok = false;
- _result.Status = StatusCodes.Status400BadRequest;
- _result.Message = e.Message;
- }
- return _result;
- }
- }
- }
|