improvement: 마이페이지 조회 확장 (#249)
All checks were successful
SPMS_API/pipeline/head This commit looks good
All checks were successful
SPMS_API/pipeline/head This commit looks good
Reviewed-on: https://git.ipstein.myds.me/SPMS/SPMS_API/pulls/250
This commit is contained in:
commit
335676a282
|
|
@ -53,4 +53,20 @@ public class ProfileController : ControllerBase
|
||||||
var result = await _authService.UpdateProfileAsync(adminId, request);
|
var result = await _authService.UpdateProfileAsync(adminId, request);
|
||||||
return Ok(ApiResponse<ProfileResponseDto>.Success(result));
|
return Ok(ApiResponse<ProfileResponseDto>.Success(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("activity/list")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "활동 내역 조회",
|
||||||
|
Description = "현재 로그인된 관리자의 활동 내역을 페이징 조회합니다.")]
|
||||||
|
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<ActivityListResponseDto>))]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
public async Task<IActionResult> GetActivityListAsync([FromBody] ActivityListRequestDto request)
|
||||||
|
{
|
||||||
|
var adminIdClaim = User.FindFirst("adminId")?.Value;
|
||||||
|
if (string.IsNullOrEmpty(adminIdClaim) || !long.TryParse(adminIdClaim, out var adminId))
|
||||||
|
throw SpmsException.Unauthorized("인증 정보가 유효하지 않습니다.");
|
||||||
|
|
||||||
|
var result = await _authService.GetActivityListAsync(adminId, request);
|
||||||
|
return Ok(ApiResponse<ActivityListResponseDto>.Success(result));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
SPMS.Application/DTOs/Account/ActivityListRequestDto.cs
Normal file
18
SPMS.Application/DTOs/Account/ActivityListRequestDto.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class ActivityListRequestDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("page")]
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
|
||||||
|
[JsonPropertyName("size")]
|
||||||
|
public int Size { get; set; } = 10;
|
||||||
|
|
||||||
|
[JsonPropertyName("from")]
|
||||||
|
public DateTime? From { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("to")]
|
||||||
|
public DateTime? To { get; set; }
|
||||||
|
}
|
||||||
31
SPMS.Application/DTOs/Account/ActivityListResponseDto.cs
Normal file
31
SPMS.Application/DTOs/Account/ActivityListResponseDto.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using SPMS.Application.DTOs.Notice;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class ActivityListResponseDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public List<ActivityItemDto> Items { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("pagination")]
|
||||||
|
public PaginationDto Pagination { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ActivityItemDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("activity_type")]
|
||||||
|
public string ActivityType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ip_address")]
|
||||||
|
public string? IpAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("occurred_at")]
|
||||||
|
public DateTime OccurredAt { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -21,4 +21,10 @@ public class ProfileResponseDto
|
||||||
|
|
||||||
[JsonPropertyName("created_at")]
|
[JsonPropertyName("created_at")]
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("last_login_at")]
|
||||||
|
public DateTime? LastLoginAt { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("organization")]
|
||||||
|
public string? Organization { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace SPMS.Application.DTOs.Account;
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
|
@ -10,4 +11,8 @@ public class UpdateProfileRequestDto
|
||||||
[Phone(ErrorMessage = "올바른 전화번호 형식이 아닙니다.")]
|
[Phone(ErrorMessage = "올바른 전화번호 형식이 아닙니다.")]
|
||||||
[StringLength(20, ErrorMessage = "전화번호는 20자 이내여야 합니다.")]
|
[StringLength(20, ErrorMessage = "전화번호는 20자 이내여야 합니다.")]
|
||||||
public string? Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("organization")]
|
||||||
|
[StringLength(100, ErrorMessage = "소속은 100자 이내여야 합니다.")]
|
||||||
|
public string? Organization { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ public interface IAuthService
|
||||||
Task IssueTempPasswordAsync(TempPasswordRequestDto request);
|
Task IssueTempPasswordAsync(TempPasswordRequestDto request);
|
||||||
Task<ProfileResponseDto> GetProfileAsync(long adminId);
|
Task<ProfileResponseDto> GetProfileAsync(long adminId);
|
||||||
Task<ProfileResponseDto> UpdateProfileAsync(long adminId, UpdateProfileRequestDto request);
|
Task<ProfileResponseDto> UpdateProfileAsync(long adminId, UpdateProfileRequestDto request);
|
||||||
|
Task<ActivityListResponseDto> GetActivityListAsync(long adminId, ActivityListRequestDto request);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class AuthService : IAuthService
|
||||||
private readonly JwtSettings _jwtSettings;
|
private readonly JwtSettings _jwtSettings;
|
||||||
private readonly ITokenStore _tokenStore;
|
private readonly ITokenStore _tokenStore;
|
||||||
private readonly IEmailService _emailService;
|
private readonly IEmailService _emailService;
|
||||||
|
private readonly IRepository<SystemLog> _systemLogRepository;
|
||||||
private readonly ILogger<AuthService> _logger;
|
private readonly ILogger<AuthService> _logger;
|
||||||
|
|
||||||
public AuthService(
|
public AuthService(
|
||||||
|
|
@ -30,6 +31,7 @@ public class AuthService : IAuthService
|
||||||
IOptions<JwtSettings> jwtSettings,
|
IOptions<JwtSettings> jwtSettings,
|
||||||
ITokenStore tokenStore,
|
ITokenStore tokenStore,
|
||||||
IEmailService emailService,
|
IEmailService emailService,
|
||||||
|
IRepository<SystemLog> systemLogRepository,
|
||||||
ILogger<AuthService> logger)
|
ILogger<AuthService> logger)
|
||||||
{
|
{
|
||||||
_adminRepository = adminRepository;
|
_adminRepository = adminRepository;
|
||||||
|
|
@ -38,6 +40,7 @@ public class AuthService : IAuthService
|
||||||
_jwtSettings = jwtSettings.Value;
|
_jwtSettings = jwtSettings.Value;
|
||||||
_tokenStore = tokenStore;
|
_tokenStore = tokenStore;
|
||||||
_emailService = emailService;
|
_emailService = emailService;
|
||||||
|
_systemLogRepository = systemLogRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -610,7 +613,9 @@ public class AuthService : IAuthService
|
||||||
Name = admin.Name,
|
Name = admin.Name,
|
||||||
Phone = admin.Phone,
|
Phone = admin.Phone,
|
||||||
Role = (int)admin.Role,
|
Role = (int)admin.Role,
|
||||||
CreatedAt = admin.CreatedAt
|
CreatedAt = admin.CreatedAt,
|
||||||
|
LastLoginAt = admin.LastLoginAt,
|
||||||
|
Organization = admin.Organization
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,6 +644,12 @@ public class AuthService : IAuthService
|
||||||
hasChange = true;
|
hasChange = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.Organization is not null && request.Organization != admin.Organization)
|
||||||
|
{
|
||||||
|
admin.Organization = request.Organization;
|
||||||
|
hasChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasChange)
|
if (!hasChange)
|
||||||
{
|
{
|
||||||
throw new SpmsException(
|
throw new SpmsException(
|
||||||
|
|
@ -657,7 +668,48 @@ public class AuthService : IAuthService
|
||||||
Name = admin.Name,
|
Name = admin.Name,
|
||||||
Phone = admin.Phone,
|
Phone = admin.Phone,
|
||||||
Role = (int)admin.Role,
|
Role = (int)admin.Role,
|
||||||
CreatedAt = admin.CreatedAt
|
CreatedAt = admin.CreatedAt,
|
||||||
|
LastLoginAt = admin.LastLoginAt,
|
||||||
|
Organization = admin.Organization
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ActivityListResponseDto> GetActivityListAsync(long adminId, ActivityListRequestDto request)
|
||||||
|
{
|
||||||
|
var page = request.Page > 0 ? request.Page : 1;
|
||||||
|
var size = request.Size > 0 ? request.Size : 10;
|
||||||
|
|
||||||
|
// 기간 필터 + AdminId 조건 조합
|
||||||
|
System.Linq.Expressions.Expression<Func<SystemLog, bool>> predicate = log =>
|
||||||
|
log.AdminId == adminId
|
||||||
|
&& (request.From == null || log.CreatedAt >= request.From.Value)
|
||||||
|
&& (request.To == null || log.CreatedAt <= request.To.Value);
|
||||||
|
|
||||||
|
var (items, totalCount) = await _systemLogRepository.GetPagedAsync(
|
||||||
|
page, size,
|
||||||
|
predicate,
|
||||||
|
log => log.CreatedAt,
|
||||||
|
descending: true);
|
||||||
|
|
||||||
|
var totalPages = (int)Math.Ceiling((double)totalCount / size);
|
||||||
|
|
||||||
|
return new ActivityListResponseDto
|
||||||
|
{
|
||||||
|
Items = items.Select(log => new ActivityItemDto
|
||||||
|
{
|
||||||
|
ActivityType = log.Action,
|
||||||
|
Title = log.TargetType ?? log.Action,
|
||||||
|
Description = log.Details,
|
||||||
|
IpAddress = log.IpAddress,
|
||||||
|
OccurredAt = log.CreatedAt
|
||||||
|
}).ToList(),
|
||||||
|
Pagination = new DTOs.Notice.PaginationDto
|
||||||
|
{
|
||||||
|
Page = page,
|
||||||
|
Size = size,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
TotalPages = totalPages
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,5 @@ public class Admin : BaseEntity
|
||||||
public DateTime AgreedAt { get; set; }
|
public DateTime AgreedAt { get; set; }
|
||||||
public bool MustChangePassword { get; set; }
|
public bool MustChangePassword { get; set; }
|
||||||
public DateTime? TempPasswordIssuedAt { get; set; }
|
public DateTime? TempPasswordIssuedAt { get; set; }
|
||||||
|
public string? Organization { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1253
SPMS.Infrastructure/Migrations/20260226005844_AddOrganizationToAdmin.Designer.cs
generated
Normal file
1253
SPMS.Infrastructure/Migrations/20260226005844_AddOrganizationToAdmin.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,30 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace SPMS.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOrganizationToAdmin : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Organization",
|
||||||
|
table: "Admin",
|
||||||
|
type: "varchar(100)",
|
||||||
|
maxLength: 100,
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Organization",
|
||||||
|
table: "Admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -79,6 +79,10 @@ namespace SPMS.Infrastructure.Migrations
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("varchar(50)");
|
.HasColumnType("varchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Organization")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("varchar(100)");
|
||||||
|
|
||||||
b.Property<string>("Password")
|
b.Property<string>("Password")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public class AdminConfiguration : IEntityTypeConfiguration<Admin>
|
||||||
builder.Property(e => e.AgreeTerms).HasColumnType("tinyint(1)").IsRequired();
|
builder.Property(e => e.AgreeTerms).HasColumnType("tinyint(1)").IsRequired();
|
||||||
builder.Property(e => e.AgreePrivacy).HasColumnType("tinyint(1)").IsRequired();
|
builder.Property(e => e.AgreePrivacy).HasColumnType("tinyint(1)").IsRequired();
|
||||||
builder.Property(e => e.AgreedAt).IsRequired();
|
builder.Property(e => e.AgreedAt).IsRequired();
|
||||||
|
builder.Property(e => e.Organization).HasMaxLength(100);
|
||||||
|
|
||||||
builder.HasQueryFilter(e => !e.IsDeleted);
|
builder.HasQueryFilter(e => !e.IsDeleted);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user