KIM-JINO5 3 сар өмнө
parent
commit
b8af1777d3
87 өөрчлөгдсөн 8708 нэмэгдсэн , 129 устгасан
  1. 4 2
      .claude/settings.local.json
  2. 4 1
      Admin/Admin.csproj
  3. 25 2
      Admin/Areas/Identity/Pages/Account/Login.cshtml.cs
  4. 157 0
      Admin/Middlewares/AdminAccessLogMiddleware.cs
  5. 3 3
      Admin/Pages/Banner/List/Index.cshtml
  6. 1 1
      Admin/Pages/Banner/Position.cshtml
  7. 122 0
      Admin/Pages/Channel/List/Edit.cshtml
  8. 133 0
      Admin/Pages/Channel/List/Edit.cshtml.cs
  9. 146 0
      Admin/Pages/Channel/List/Index.cshtml
  10. 108 0
      Admin/Pages/Channel/List/Index.cshtml.cs
  11. 74 0
      Admin/Pages/Channel/List/View.cshtml
  12. 44 0
      Admin/Pages/Channel/List/View.cshtml.cs
  13. 198 0
      Admin/Pages/Channel/List/Write.cshtml
  14. 126 0
      Admin/Pages/Channel/List/Write.cshtml.cs
  15. 193 0
      Admin/Pages/Director/AccessLog/Index.cshtml
  16. 119 0
      Admin/Pages/Director/AccessLog/Index.cshtml.cs
  17. 165 0
      Admin/Pages/Director/LoginLog/Index.cshtml
  18. 115 0
      Admin/Pages/Director/LoginLog/Index.cshtml.cs
  19. 8 1
      Admin/Pages/Director/User/Index.cshtml
  20. 2 6
      Admin/Pages/Director/User/Roles.cshtml
  21. 3 3
      Admin/Pages/Document/Index.cshtml
  22. 1 1
      Admin/Pages/Faq/Category.cshtml
  23. 4 4
      Admin/Pages/Faq/List/Index.cshtml
  24. 1 1
      Admin/Pages/Forum/Posts/List/Edit.cshtml
  25. 1 1
      Admin/Pages/Forum/Posts/List/Edit.cshtml.cs
  26. 2 2
      Admin/Pages/Member/Grade/Index.cshtml
  27. 3 3
      Admin/Pages/Member/List/Index.cshtml
  28. 3 3
      Admin/Pages/Member/Log/Email.cshtml
  29. 3 3
      Admin/Pages/Member/Log/Intro.cshtml
  30. 3 3
      Admin/Pages/Member/Log/Login/Index.cshtml
  31. 3 3
      Admin/Pages/Member/Log/Name.cshtml
  32. 3 3
      Admin/Pages/Member/Log/Summary.cshtml
  33. 2 2
      Admin/Pages/Member/Wallet/List/Index.cshtml
  34. 5 5
      Admin/Pages/Member/Wallet/Transactions/Index.cshtml
  35. 0 20
      Admin/Pages/Member/_navTabs.cshtml
  36. 3 3
      Admin/Pages/Popup/Index.cshtml
  37. 9 3
      Admin/Pages/Shared/_MenuItem.cshtml
  38. 2 0
      Admin/Pages/Shared/_Pagination.cshtml
  39. 6 6
      Admin/Program.cs
  40. 16 1
      Admin/using.cs
  41. 1 1
      Admin/wwwroot/js/site.js
  42. 4 0
      Application/Abstractions/Data/IAppDbContext.cs
  43. 14 0
      Application/Features/Channel/List/Create/Command.cs
  44. 38 0
      Application/Features/Channel/List/Create/Handler.cs
  45. 5 0
      Application/Features/Channel/List/Delete/Command.cs
  46. 27 0
      Application/Features/Channel/List/Delete/Handler.cs
  47. 34 0
      Application/Features/Channel/List/Get/Handler.cs
  48. 5 0
      Application/Features/Channel/List/Get/Query.cs
  49. 19 0
      Application/Features/Channel/List/Get/Response.cs
  50. 91 0
      Application/Features/Channel/List/Search/Handler.cs
  51. 13 0
      Application/Features/Channel/List/Search/Query.cs
  52. 21 0
      Application/Features/Channel/List/Search/Response.cs
  53. 13 0
      Application/Features/Channel/List/Update/Command.cs
  54. 28 0
      Application/Features/Channel/List/Update/Handler.cs
  55. 5 0
      Application/Features/Director/AccessLog/Delete/Command.cs
  56. 18 0
      Application/Features/Director/AccessLog/Delete/Handler.cs
  57. 91 0
      Application/Features/Director/AccessLog/Search/Handler.cs
  58. 13 0
      Application/Features/Director/AccessLog/Search/Query.cs
  59. 25 0
      Application/Features/Director/AccessLog/Search/Response.cs
  60. 5 0
      Application/Features/Director/LoginLog/Delete/Command.cs
  61. 18 0
      Application/Features/Director/LoginLog/Delete/Handler.cs
  62. 173 0
      Application/Features/Director/LoginLog/Search/Handler.cs
  63. 13 0
      Application/Features/Director/LoginLog/Search/Query.cs
  64. 23 0
      Application/Features/Director/LoginLog/Search/Response.cs
  65. 2 1
      Application/Features/Member/List/Create/Handler.cs
  66. 64 0
      Domain/Entities/Director/AdminAccessLog.cs
  67. 67 0
      Domain/Entities/Director/AdminLoginLog.cs
  68. 1 1
      Domain/Entities/EmailVerification/ValueObject/AdditionalData.cs
  69. 1 1
      Domain/Entities/Members/Channel.cs
  70. 1 1
      Domain/Entities/Members/Logs/MemberEmailChangeLog.cs
  71. 6 6
      Domain/Entities/Members/Logs/MemberIntroChangeLog.cs
  72. 2 2
      Domain/Entities/Members/Logs/MemberLoginLog.cs
  73. 6 6
      Domain/Entities/Members/Logs/MemberNameChangeLog.cs
  74. 6 6
      Domain/Entities/Members/Logs/MemberSummaryChangeLog.cs
  75. 1 1
      Domain/Entities/Page/Banner/Item.cs
  76. 1 1
      Domain/Entities/Page/Banner/Position.cs
  77. 1 1
      Domain/Entities/Page/Document.cs
  78. 1 1
      Domain/Entities/Page/Faq/Category.cs
  79. 1 1
      Domain/Entities/Page/Faq/Item.cs
  80. 1 1
      Domain/Entities/Page/Popup.cs
  81. 9 4
      Infrastructure/Persistence/AppDbContext.cs
  82. 30 0
      Infrastructure/Persistence/Configurations/Director/AdminAccessLogConfiguration.cs
  83. 24 0
      Infrastructure/Persistence/Configurations/Director/AdminLoginLogConfiguration.cs
  84. 5718 0
      Infrastructure/Persistence/Migrations/20260212015759_a6.Designer.cs
  85. 88 0
      Infrastructure/Persistence/Migrations/20260212015759_a6.cs
  86. 126 0
      Infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs
  87. 34 8
      SharedKernel/Constants/Menus.cs

+ 4 - 2
.claude/settings.local.json

@@ -11,11 +11,13 @@
       "FileEdit(E:\\workspace\\bitforum.io:*)",
       "FileRead(E:\\workspace\\bitforum.io:*)",
       "FileCreate(E:\\workspace\\bitforum.io:*)",
-      "FileDelete(E:\\workspace\\bitforum.io:*)"
+      "FileDelete(E:\\workspace\\bitforum.io:*)",
+      "Bash(findstr:*)",
+      "Bash(find:*)"
     ]
   },
   "dangerouslySkipPermissions": true,
   "allowedPaths": [
     "E:/workspace/bitforum.io"
   ]
-}
+}

+ 4 - 1
Admin/Admin.csproj

@@ -6,6 +6,10 @@
 		<ImplicitUsings>enable</ImplicitUsings>
 	</PropertyGroup>
 
+	<ItemGroup>
+	  <None Remove="nul" />
+	</ItemGroup>
+
 	<ItemGroup>
 		<None Include=".github\copilot-instructions.md" />
 	</ItemGroup>
@@ -32,7 +36,6 @@
 	</ItemGroup>
 
 	<ItemGroup>
-		<Folder Include="Middleware\" />
 		<Folder Include="wwwroot\uploads\basic\" />
 		<Folder Include="wwwroot\uploads\banner\" />
 	</ItemGroup>

+ 25 - 2
Admin/Areas/Identity/Pages/Account/Login.cshtml.cs

@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 #nullable disable
 
+using Application.Abstractions.Data;
+using Domain.Entities.Director;
 using Infrastructure.Persistence.Identity;
 using Microsoft.AspNetCore.Authentication;
 using Microsoft.AspNetCore.Authorization;
@@ -22,11 +24,13 @@ namespace Admin.Areas.Identity.Pages.Account
     {
         private readonly SignInManager<ApplicationUser> _signInManager;
         private readonly ILogger<LoginModel> _logger;
+        private readonly IAppDbContext _db;
 
-        public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger)
+        public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IAppDbContext db)
         {
             _signInManager = signInManager;
             _logger = logger;
+            _db = db;
         }
 
         /// <summary>
@@ -81,7 +85,7 @@ namespace Admin.Areas.Identity.Pages.Account
             ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
             ///     directly from your code. This API may change or be removed in future releases.
             /// </summary>
-            [Display(Name = "·Î±×ÀÎ »óÅ À¯Áö")]
+            [Display(Name = "��� ���� ����")]
             public bool RememberMe { get; set; }
         }
 
@@ -110,25 +114,44 @@ namespace Admin.Areas.Identity.Pages.Account
 
             if (ModelState.IsValid)
             {
+                var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
+                var userAgent = HttpContext.Request.Headers.UserAgent.ToString();
+                if (userAgent?.Length > 512)
+                {
+                    userAgent = userAgent[..512];
+                }
+
                 // This doesn't count login failures towards account lockout
                 // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                 var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
                 if (result.Succeeded)
                 {
+                    await _db.AdminLoginLog.AddAsync(AdminLoginLog.Create(true, Input.Email, ipAddress: ipAddress, userAgent: userAgent));
+                    await _db.SaveChangesAsync();
+
                     _logger.LogInformation("User logged in.");
                     return LocalRedirect(returnUrl);
                 }
                 if (result.RequiresTwoFactor)
                 {
+                    await _db.AdminLoginLog.AddAsync(AdminLoginLog.Create(true, Input.Email, reason: "2FA í•„ìš”", ipAddress: ipAddress, userAgent: userAgent));
+                    await _db.SaveChangesAsync();
+
                     return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
                 }
                 if (result.IsLockedOut)
                 {
+                    await _db.AdminLoginLog.AddAsync(AdminLoginLog.Create(false, Input.Email, reason: "계정 잠김", ipAddress: ipAddress, userAgent: userAgent));
+                    await _db.SaveChangesAsync();
+
                     _logger.LogWarning("User account locked out.");
                     return RedirectToPage("./Lockout");
                 }
                 else
                 {
+                    await _db.AdminLoginLog.AddAsync(AdminLoginLog.Create(false, Input.Email, reason: "로그� 실패", ipAddress: ipAddress, userAgent: userAgent));
+                    await _db.SaveChangesAsync();
+
                     ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                     return Page();
                 }

+ 157 - 0
Admin/Middlewares/AdminAccessLogMiddleware.cs

@@ -0,0 +1,157 @@
+using SharedKernel.Constants;
+using Application.Abstractions.Data;
+using Domain.Entities.Director;
+using System.Diagnostics;
+
+namespace Admin.Middlewares;
+
+public class AdminAccessLogMiddleware(RequestDelegate next)
+{
+    // 로깅 제외 경로
+    private static readonly string[] ExcludePrefixes =
+    [
+        "/lib/",
+        "/css/",
+        "/js/",
+        "/images/",
+        "/favicon",
+        "/_framework",
+        "/_blazor",
+        "/.well-known/",
+        "/Identity/Account"
+    ];
+
+    // 로깅 제외 확장자
+    private static readonly string[] ExcludeExtensions =
+    [
+        ".css",
+        ".js",
+        ".map",
+        ".png",
+        ".jpg",
+        ".jpeg",
+        ".gif",
+        ".svg",
+        ".ico",
+        ".woff",
+        ".woff2",
+        ".ttf",
+        ".eot"
+    ];
+
+    public async Task InvokeAsync(HttpContext context)
+    {
+        var path = context.Request.Path.Value ?? "";
+
+        // 정적 파일, Identity 페이지 제외
+        if (ShouldSkip(path))
+        {
+            await next(context);
+            return;
+        }
+
+        // 인증되지 않은 사용자 제외
+        if (context.User.Identity is not { IsAuthenticated: true })
+        {
+            await next(context);
+            return;
+        }
+
+        var sw = Stopwatch.StartNew(); // 시작 시간
+
+        await next(context);
+
+        sw.Stop(); // 종료 시간
+
+        try
+        {
+            var db = context.RequestServices.GetRequiredService<IAppDbContext>();
+
+            var userID = context.User.Identity.Name ?? "-";
+            var userName = context.User.FindFirst(System.Security.Claims.ClaimTypes.GivenName)?.Value;
+            var method = context.Request.Method;
+            var queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : null;
+            var statusCode = context.Response.StatusCode;
+            var elapsedMs = sw.ElapsedMilliseconds;
+            var menuName = ResolveMenuName(path);
+            var ipAddress = context.Connection.RemoteIpAddress?.ToString();
+            var userAgent = context.Request.Headers.UserAgent.ToString();
+
+            var log = AdminAccessLog.Create(
+                userID,
+                userName,
+                method,
+                path,
+                queryString,
+                statusCode,
+                elapsedMs,
+                menuName,
+                ipAddress,
+                userAgent);
+
+            await db.AdminAccessLog.AddAsync(log);
+            await db.SaveChangesAsync();
+        }
+        catch
+        {
+
+        }
+    }
+
+    // 로깅 제외 경로 및 확장자 확인
+    private static bool ShouldSkip(string path)
+    {
+        foreach (var prefix in ExcludePrefixes)
+        {
+            if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+            {
+                return true;
+            }
+        }
+
+        foreach (var ext in ExcludeExtensions)
+        {
+            if (path.EndsWith(ext, StringComparison.OrdinalIgnoreCase))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    // 경로에 해당하는 메뉴 이름 찾기
+    private static string? ResolveMenuName(string path)
+    {
+        var menus = Menus.GetMenus();
+
+        return FindMenuName(menus, path.TrimEnd('/'));
+    }
+
+    // 재귀적으로 메뉴 트리 탐색
+    private static string? FindMenuName(List<Menu> menus, string path)
+    {
+        foreach (var menu in menus)
+        {
+            if (!string.IsNullOrEmpty(menu.Path))
+            {
+                var menuPath = menu.Path.TrimEnd('/');
+                if (path.Equals(menuPath, StringComparison.OrdinalIgnoreCase) || path.StartsWith(menuPath + "/", StringComparison.OrdinalIgnoreCase))
+                {
+                    return menu.Name;
+                }
+            }
+
+            if (menu.Children is { Count: > 0 })
+            {
+                var childResult = FindMenuName(menu.Children, path);
+                if (childResult is not null)
+                {
+                    return childResult;
+                }
+            }
+        }
+
+        return null;
+    }
+}

+ 3 - 3
Admin/Pages/Banner/List/Index.cshtml

@@ -9,7 +9,7 @@
     <hr />
 
     <partial name="_StatusMessage" />
-    <partial name="_navTabs" />
+    <partial name="_NavTabs" />
 
     <div class="row g-2 mb-2 mb-sm-0 mt-2">
         <div class="col-auto">
@@ -62,7 +62,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -89,7 +89,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 1 - 1
Admin/Pages/Banner/Position.cshtml

@@ -9,7 +9,7 @@
     <hr />
 
     <partial name="_StatusMessage" />
-    <partial name="_navTabs" />
+    <partial name="_NavTabs" />
 
     <div class="row g-2 align-items-end mt-2">
         <div class="col">

+ 122 - 0
Admin/Pages/Channel/List/Edit.cshtml

@@ -0,0 +1,122 @@
+@page "{id:int}"
+@model Admin.Pages.Channel.List.EditModel
+@{
+    ViewData["Title"] = "채널 수정";
+}
+
+<div class="container">
+    <h3>@ViewData["Title"]</h3>
+    <hr />
+
+    <partial name="_StatusMessage" />
+
+    <div class="alert alert-success" role="alert">
+        채널 등록 시 YouTube API를 통해 자동 수집된 정보입니다.<br />
+        관리자가 직접 수정 시 잘못된 결과가 발생할 수 있습니다.
+    </div>
+
+    <form id="fAdminWrite" method="post" accept-charset="utf-8" autocomplete="off">
+        <input type="hidden" asp-for="Input.ID" />
+        <input type="hidden" asp-for="Input.MemberID" />
+
+        <div class="row mb-2">
+            <label class="col-sm-2 col-form-label">PK</label>
+            <div class="col-sm-10">
+                <input type="text" readonly class="form-control-plaintext" value="@Model.Input.ID" />
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label class="col-sm-2 col-form-label">회원(소유자)</label>
+            <div class="col-sm-10">
+                <input type="text" readonly class="form-control-plaintext" value="[@Model.Input.MemberID] @Model.MemberInfo" />
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.Name" class="col-sm-2 col-form-label"><span class="text-danger">*</span> 이름</label>
+            <div class="col-sm-10">
+                <input type="text" asp-for="Input.Name" class="form-control" required maxlength="200" />
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.Handle" class="col-sm-2 col-form-label">핸들</label>
+            <div class="col-sm-10">
+                <div class="input-group">
+                    <span class="input-group-text">@@</span>
+                    <input type="text" asp-for="Input.Handle" class="form-control" maxlength="30" />
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.YouTubeUrl" class="col-sm-2 col-form-label"><span class="text-danger">*</span> YouTube 주소</label>
+            <div class="col-sm-10">
+                <input type="url" asp-for="Input.YouTubeUrl" class="form-control" required maxlength="255" />
+                <div class="text-muted form-text">
+                    YouTube 채널 주소 (예: https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx)
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.PlatformFeeRate" class="col-sm-2 col-form-label"><span class="text-danger">*</span> 수수료(%)</label>
+            <div class="col-sm-10">
+                <div class="row">
+                    <div class="col col-md-auto">
+                        <div class="input-group">
+                            <input type="number" asp-for="Input.PlatformFeeRate" class="form-control" required min="0" max="100" step="0.1" />
+                            <span class="input-group-text">%</span>
+                        </div>
+                    </div>
+                </div>
+                <span asp-validation-for="Input.PlatformFeeRate" class="text-danger"></span>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.IsVerified" class="col-sm-2 col-form-label">인증 여부</label>
+            <div class="col-sm-10 align-content-center">
+                <div class="form-check form-check-inline">
+                    <input type="checkbox" asp-for="Input.IsVerified" class="form-check-input" />
+                    <label class="form-check-label" asp-for="Input.IsVerified">인증합니다.</label>
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.IsActive" class="col-sm-2 col-form-label">사용 여부</label>
+            <div class="col-sm-10 align-content-center">
+                <div class="form-check form-check-inline">
+                    <input type="checkbox" asp-for="Input.IsActive" class="form-check-input" />
+                    <label class="form-check-label" asp-for="Input.IsActive">사용합니다.</label>
+                </div>
+            </div>
+        </div>
+        @if (Model.Input.UpdatedAt is not null)
+        {
+            <div class="row mb-2">
+                <label class="col-sm-2 col-form-label">수정일시</label>
+                <div class="col-sm-10">
+                    <input type="text" class="form-control-plaintext" readonly value="@Model.Input.UpdatedAt" />
+                </div>
+            </div>
+        }
+        <div class="row mb-2">
+            <label class="col-sm-2 col-form-label">등록일시</label>
+            <div class="col-sm-10">
+                <input type="text" class="form-control-plaintext" readonly value="@Model.Input.CreatedAt" />
+            </div>
+        </div>
+
+        <hr />
+
+        <div class="d-grid gap-2 text-center d-md-block">
+            <button type="submit" class="btn btn-success">저장</button>
+            <a href="@(Model.ReturnUrl ?? "/Channel/List")" class="btn btn-secondary">취소</a>
+            <button type="submit"
+                    class="btn btn-danger"
+                    formaction="?handler=Delete"
+                    formnovalidate
+                    onclick="return confirm('삭제 하시겠습니까?');">
+                삭제
+            </button>
+        </div>
+
+        <br />
+    </form>
+</div>

+ 133 - 0
Admin/Pages/Channel/List/Edit.cshtml.cs

@@ -0,0 +1,133 @@
+using SharedKernel.Extensions;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace Admin.Pages.Channel.List
+{
+    public class EditModel(IMediator mediator) : PageModel
+    {
+        [BindProperty]
+        public string? QueryString { get; set; }
+
+        public string? ReturnUrl { get; set; }
+
+        [BindProperty]
+        public InputModel Input { get; set; } = new();
+
+        public string? MemberInfo { get; set; }
+
+        public sealed class InputModel
+        {
+            [Required(ErrorMessage = "ID는 필수입니다.")]
+            public int ID { get; set; }
+
+            public int MemberID { get; set; }
+
+            [DisplayName("이름")]
+            [Required(ErrorMessage = "{0}은 필수입니다.")]
+            [StringLength(200, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
+            public string Name { get; set; } = default!;
+
+            [DisplayName("핸들")]
+            [StringLength(30, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
+            public string? Handle { get; set; }
+
+            [DisplayName("YouTube 주소")]
+            [Required(ErrorMessage = "{0}은 필수입니다.")]
+            [StringLength(255, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
+            public string YouTubeUrl { get; set; } = default!;
+
+            [DisplayName("수수료(%)")]
+            [Range(0, 100, ErrorMessage = "{0}은 {1}~{2} 사이여야 합니다.")]
+            public decimal PlatformFeeRate { get; set; }
+
+            [DisplayName("인증 여부")]
+            public bool IsVerified { get; set; }
+
+            [DisplayName("사용 여부")]
+            public bool IsActive { get; set; }
+
+            public string? UpdatedAt { get; set; }
+            public string CreatedAt { get; set; } = default!;
+        }
+
+        public async Task OnGetAsync(int id, CancellationToken ct)
+        {
+            ReturnUrl = Request.Headers.Referer.ToString();
+
+            var result = await mediator.Send(new GetChannel.Query(id), ct);
+            if (result is null)
+            {
+                return;
+            }
+
+            MemberInfo = $"{result.MemberEmail}, {result.MemberName ?? result.MemberSID ?? "-"}";
+
+            Input = new InputModel
+            {
+                ID = result.ID,
+                MemberID = result.MemberID,
+                Name = result.Name,
+                Handle = result.Handle,
+                YouTubeUrl = result.YouTubeUrl,
+                PlatformFeeRate = result.PlatformFeeRate,
+                IsVerified = result.IsVerified,
+                IsActive = result.IsActive,
+                UpdatedAt = result.UpdatedAt.GetDateAt(),
+                CreatedAt = result.CreatedAt.GetDateAt()
+            };
+
+            QueryString = Request.QueryString.ToString();
+        }
+
+        public async Task<IActionResult> OnPostAsync(CancellationToken ct)
+        {
+            try
+            {
+                if (!ModelState.IsValid)
+                {
+                    return Page();
+                }
+
+                await mediator.Send(new UpdateChannel.Command(
+                    Input.ID,
+                    Input.Name,
+                    Input.Handle,
+                    Input.YouTubeUrl,
+                    Input.PlatformFeeRate,
+                    Input.IsVerified,
+                    Input.IsActive
+                ), ct);
+
+                TempData["SuccessMessage"] = "채널이 수정되었습니다.";
+            }
+            catch (Exception e)
+            {
+                TempData["ErrorMessages"] = e.Message;
+            }
+
+            return Redirect($"/Channel/List/Edit/{Input.ID}{Request.QueryString}");
+        }
+
+        public async Task<IActionResult> OnPostDeleteAsync(CancellationToken ct)
+        {
+            try
+            {
+                await mediator.Send(new DeleteChannel.Command([Input.ID]), ct);
+
+                TempData["SuccessMessage"] = "채널이 삭제되었습니다.";
+
+                return RedirectToPage("/Channel/List/Index");
+            }
+            catch (Exception e)
+            {
+                TempData["ErrorMessages"] = e.Message;
+
+                return Redirect($"/Channel/List/Edit/{Input.ID}{Request.QueryString}");
+            }
+        }
+    }
+}

+ 146 - 0
Admin/Pages/Channel/List/Index.cshtml

@@ -0,0 +1,146 @@
+@page
+@model Admin.Pages.Channel.List.IndexModel
+@{
+    ViewData["Title"] = "채널 관리";
+}
+
+<div class="container-fluid">
+    <h3>@ViewData["Title"]</h3>
+    <hr />
+
+    <partial name="_StatusMessage" />
+
+    <div class="row g-2 align-items-end">
+        <div class="col-6 col-sm-auto">
+            <select name="search" id="search" class="form-select" form="fAdminSearch">
+                <option value="1" selected="@(Model.Query.Search == 1)">채널 이름</option>
+                <option value="2" selected="@(Model.Query.Search == 2)">채널 SID</option>
+                <option value="3" selected="@(Model.Query.Search == 3)">핸들</option>
+                <option value="4" selected="@(Model.Query.Search == 4)">YouTube URL</option>
+                <option value="5" selected="@(Model.Query.Search == 5)">회원 ID</option>
+                <option value="6" selected="@(Model.Query.Search == 6)">회원 이메일</option>
+                <option value="7" selected="@(Model.Query.Search == 7)">회원 이름</option>
+            </select>
+        </div>
+        <div class="col-6 col-sm-auto">
+            <select name="isVerified" id="isVerified" class="form-select" form="fAdminSearch">
+                <option value="">인증 전체</option>
+                <option value="false" selected="@(Model.Query.IsVerified.HasValue && Model.Query.IsVerified == false)">미인증</option>
+                <option value="true" selected="@(Model.Query.IsVerified.HasValue && Model.Query.IsVerified == true)">인증</option>
+            </select>
+        </div>
+        <div class="col-12 col-sm col-md col-lg-auto">
+            <input type="text" name="keyword" id="keyword" class="form-control" maxlength="100" value="@Model.Query.Keyword" placeholder="검색어" form="fAdminSearch" />
+        </div>
+        <div class="col-12 col-md-12 col-lg-auto">
+            <div class="input-group">
+                <input type="date" name="startAt" id="startAt" class="form-control" value="@Model.Query.StartAt" form="fAdminSearch" />
+                <span class="input-group-text">~</span>
+                <input type="date" name="endAt" id="endAt" class="form-control" value="@Model.Query.EndAt" form="fAdminSearch" />
+            </div>
+        </div>
+        <div class="col-12 col-lg-auto text-center">
+            <button type="submit" id="btnSearch" class="btn btn-primary" form="fAdminSearch">검색</button>
+        </div>
+    </div>
+
+    <hr />
+
+    <div class="row g-2 align-items-end">
+        <div class="col">
+            Total : @Model?.Total.ToString("N0")
+        </div>
+        <div class="col-auto">
+            <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
+                <option value="10" selected="@(Model.Query.PerPage == 10)">10</option>
+                <option value="20" selected="@(Model.Query.PerPage == 20)">20</option>
+                <option value="50" selected="@(Model.Query.PerPage == 50)">50</option>
+                <option value="100" selected="@(Model.Query.PerPage == 100)">100</option>
+            </select>
+        </div>
+        <div class="col-auto">
+            <button type="button" id="btnListDelete" class="btn btn-danger" form="fAdminList" disabled>삭제</button>
+            <a class="btn btn-success" asp-page="/Channel/List/Write">추가</a>
+        </div>
+    </div>
+
+    <div class="table-responsive">
+        <table class="table table-striped table-bordered table-hover mt-3">
+            <thead>
+                <tr>
+                    <th>
+                        <div class="form-check form-check-inline">
+                            <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
+                            <label for="checkedAll">ID</label>
+                        </div>
+                    </th>
+                    <th>SID</th>
+                    <th>이름</th>
+                    <th>핸들</th>
+                    <th>소유자</th>
+                    <th>인증</th>
+                    <th>사용</th>
+                    <th>등록일시</th>
+                </tr>
+            </thead>
+            <tbody>
+                @if (Model.List.Count == 0)
+                {
+                    <tr>
+                        <td colspan="9">No data.</td>
+                    </tr>
+                }
+                else
+                {
+                    @foreach (var row in Model.List)
+                    {
+                        <tr>
+                            <td>
+                                <div class="form-check form-check-inline">
+                                    <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
+                                    <label for="ids_@row.ID">@row.ID</label>
+                                </div>
+                            </td>
+                            <td>@row.SID</td>
+                            <td><a href="/Channel/List/Edit/@row.ID">@row.Name</a></td>
+                            <td>@row.Handle</td>
+                            <td>[@row.MemberID] @row.MemberName</td>
+                            <td>@row.IsVerified</td>
+                            <td>@row.IsActive</td>
+                            <td>@row.CreatedAt</td>
+                        </tr>
+                    }
+                }
+            </tbody>
+        </table>
+
+        <partial name="_Pagination" model="Model.Pagination" />
+    </div>
+</div>
+
+<form id="fAdminSearch" method="get" accept-charset="utf-8">
+    <input type="hidden" name="pageNum" value="1" />
+    <input type="hidden" name="perPage" value="@Model.Query.PerPage" />
+</form>
+
+<form id="fAdminList" method="post" accept-charset="utf-8">
+    @Html.AntiForgeryToken()
+    <input type="hidden" name="pageNum" value="@Model.Query.PageNum" />
+    <input type="hidden" name="perPage" value="@Model.Query.PerPage" />
+    <input type="hidden" name="search" value="@Model.Query.Search" />
+    <input type="hidden" name="keyword" value="@Model.Query.Keyword" />
+    <input type="hidden" name="startAt" value="@Model.Query.StartAt" />
+    <input type="hidden" name="endAt" value="@Model.Query.EndAt" />
+    <input type="hidden" name="isVerified" value="@Model.Query.IsVerified" />
+</form>
+
+@section Scripts {
+    <script>
+        let searchForm = document.getElementById("fAdminSearch");
+
+        $(document).on("change", "#perPage", function () {
+            searchForm.elements["pageNum"].value = "1";
+            searchForm.submit();
+        });
+    </script>
+}

+ 108 - 0
Admin/Pages/Channel/List/Index.cshtml.cs

@@ -0,0 +1,108 @@
+using SharedKernel.Helpers;
+using SharedKernel.Extensions;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace Admin.Pages.Channel.List
+{
+    public class IndexModel(IMediator mediator) : PageModel
+    {
+        [BindProperty(SupportsGet = true)]
+        public QueryParams Query { get; set; } = new();
+
+        public sealed class QueryParams
+        {
+            public int? Search { get; set; }
+            public string? Keyword { get; set; }
+            public string? StartAt { get; set; }
+            public string? EndAt { get; set; }
+            public bool? IsVerified { get; set; }
+
+            [Range(1, int.MaxValue)]
+            [DisplayName("페이지 번호")]
+            public int PageNum { get; set; } = 1;
+
+            [Range(1, 100)]
+            [DisplayName("페이지 목록 수")]
+            public ushort PerPage { get; set; } = 20;
+        }
+
+        public int Total { get; set; } = 0;
+
+        public List<(
+            int Num,
+            int ID,
+            string SID,
+            string Name,
+            string? Handle,
+            string YouTubeUrl,
+            decimal PlatformFeeRate,
+            char IsVerified,
+            char IsActive,
+            int MemberID,
+            string? MemberName,
+            string? MemberEmail,
+            string? UpdatedAt,
+            string CreatedAt
+        )> List { get; set; } = [];
+
+        public Pagination? Pagination { get; set; }
+
+        public async Task OnGetAsync(CancellationToken ct)
+        {
+            if (!ModelState.IsValid)
+            {
+                return;
+            }
+
+            var result = await mediator.Send(new SearchChannels.Query(
+                Query.Search,
+                Query.Keyword,
+                Query.IsVerified,
+                Query.StartAt,
+                Query.EndAt,
+                Query.PageNum,
+                Query.PerPage
+            ), ct);
+
+            Total = result.Total;
+            List = [..result.List.Select(c => (
+                c.Num,
+                c.ID,
+                c.SID,
+                c.Name,
+                c.Handle,
+                c.YouTubeUrl,
+                c.PlatformFeeRate,
+                c.IsVerified ? 'Y' : 'N',
+                c.IsActive ? 'Y' : 'N',
+                c.MemberID,
+                c.MemberName ?? "-",
+                c.MemberEmail ?? "-",
+                c.UpdatedAt.GetDateAt() ?? "-",
+                c.CreatedAt.GetDateAt()
+            ))];
+
+            Pagination = new Pagination(result.Total, Query.PageNum, Query.PerPage);
+        }
+
+        public async Task<IActionResult> OnPostDeleteAsync(int[] ids, CancellationToken ct)
+        {
+            try
+            {
+                await mediator.Send(new DeleteChannel.Command(ids), ct);
+
+                TempData["SuccessMessage"] = $"{ids.Length}건이 삭제되었습니다.";
+            }
+            catch (Exception e)
+            {
+                TempData["ErrorMessages"] = e.Message;
+            }
+
+            return RedirectToPage("/Channel/List/Index", Query);
+        }
+    }
+}

+ 74 - 0
Admin/Pages/Channel/List/View.cshtml

@@ -0,0 +1,74 @@
+@page "{id:int}"
+@model Admin.Pages.Channel.List.ViewModel
+@{
+    ViewData["Title"] = "채널 정보";
+}
+
+<div class="container">
+    <h3>@ViewData["Title"]</h3>
+    <hr />
+
+    <partial name="_StatusMessage" />
+
+    <div class="table-responsive">
+        <table class="table table-striped table-bordered">
+            <colgroup>
+                <col style="width: 15%;" />
+                <col />
+            </colgroup>
+            <tr>
+                <th>PK</th>
+                <td>@Model.ID</td>
+            </tr>
+            <tr>
+                <th>회원(소유자)</th>
+                <td>@Model.MemberInfo</td>
+            </tr>
+            <tr>
+                <th>SID</th>
+                <td>@Model.SID</td>
+            </tr>
+            <tr>
+                <th>이름</th>
+                <td>@Model.Name</td>
+            </tr>
+            <tr>
+                <th>핸들</th>
+                <td>@(Model.Handle ?? "-")</td>
+            </tr>
+            <tr>
+                <th>YouTube 주소</th>
+                <td>
+                    <a href="@Model.YouTubeUrl" target="_blank" rel="external">@Model.YouTubeUrl</a>
+                </td>
+            </tr>
+            <tr>
+                <th>수수료(%)</th>
+                <td>@Model.PlatformFeeRate%</td>
+            </tr>
+            <tr>
+                <th>인증 여부</th>
+                <td>@(Model.IsVerified ? "Y" : "N")</td>
+            </tr>
+            <tr>
+                <th>사용 여부</th>
+                <td>@(Model.IsActive ? "Y" : "N")</td>
+            </tr>
+            <tr>
+                <th>수정일시</th>
+                <td>@(Model.UpdatedAt ?? "-")</td>
+            </tr>
+            <tr>
+                <th>등록일시</th>
+                <td>@Model.CreatedAt</td>
+            </tr>
+        </table>
+    </div>
+
+    <div class="d-grid gap-2 text-center d-md-block">
+        <a href="/Channel/List/Edit/@Model.ID" class="btn btn-info">수정</a>
+        <a href="/Channel/List" class="btn btn-secondary">목록</a>
+    </div>
+
+    <br />
+</div>

+ 44 - 0
Admin/Pages/Channel/List/View.cshtml.cs

@@ -0,0 +1,44 @@
+using SharedKernel.Extensions;
+using MediatR;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace Admin.Pages.Channel.List
+{
+    public class ViewModel(IMediator mediator) : PageModel
+    {
+        public int ID { get; set; }
+        public int MemberID { get; set; }
+        public string? MemberInfo { get; set; }
+        public string SID { get; set; } = default!;
+        public string Name { get; set; } = default!;
+        public string? Handle { get; set; }
+        public string YouTubeUrl { get; set; } = default!;
+        public decimal PlatformFeeRate { get; set; }
+        public bool IsVerified { get; set; }
+        public bool IsActive { get; set; }
+        public string? UpdatedAt { get; set; }
+        public string CreatedAt { get; set; } = default!;
+
+        public async Task OnGetAsync(int id, CancellationToken ct)
+        {
+            var result = await mediator.Send(new GetChannel.Query(id), ct);
+            if (result is null)
+            {
+                return;
+            }
+
+            ID = result.ID;
+            MemberID = result.MemberID;
+            MemberInfo = $"[{result.MemberID}] {result.MemberEmail}, {result.MemberName ?? result.MemberSID ?? "-"}";
+            SID = result.SID;
+            Name = result.Name;
+            Handle = result.Handle;
+            YouTubeUrl = result.YouTubeUrl;
+            PlatformFeeRate = result.PlatformFeeRate;
+            IsVerified = result.IsVerified;
+            IsActive = result.IsActive;
+            UpdatedAt = result.UpdatedAt.GetDateAt();
+            CreatedAt = result.CreatedAt.GetDateAt();
+        }
+    }
+}

+ 198 - 0
Admin/Pages/Channel/List/Write.cshtml

@@ -0,0 +1,198 @@
+@page
+@model Admin.Pages.Channel.List.WriteModel
+@{
+    ViewData["Title"] = "채널 등록";
+}
+
+<div class="container">
+    <h3>@ViewData["Title"]</h3>
+    <hr />
+
+    <partial name="_StatusMessage" />
+
+    <div class="alert alert-success" role="alert">
+        채널 등록 시 YouTube API를 통해 자동 수집된 정보입니다.<br />
+        관리자가 직접 등록 시 잘못된 결과가 발생할 수 있습니다.
+    </div>
+
+    <form id="fAdminWrite" method="post" accept-charset="utf-8" autocomplete="off">
+        <div class="row mb-2">
+            <label class="col-sm-2 col-form-label"><span class="text-danger">*</span> 회원(소유자)</label>
+            <div class="col-sm-10">
+                <input type="hidden" asp-for="Input.MemberID" id="memberIdHidden" />
+                <div id="memberSelected" class="mb-1" style="display:none;">
+                    <span class="badge bg-primary fs-6 fw-normal" id="memberBadge">
+                        <span id="memberBadgeText"></span>
+                        <button type="button" class="btn-close btn-close-white ms-2" aria-label="제거" id="memberRemoveBtn" style="font-size:0.6em;vertical-align:middle;"></button>
+                    </span>
+                </div>
+                <div class="position-relative" id="memberSearchWrap">
+                    <div class="input-group">
+                        <span class="input-group-text"><i class="bi bi-search"></i></span>
+                        <input type="text" class="form-control" id="memberSearchInput" placeholder="회원 ID, SID, 이메일로 검색" autocomplete="off" />
+                    </div>
+                    <div class="list-group position-absolute w-100 shadow" id="memberSearchResults" style="z-index:1050;display:none;max-height:300px;overflow-y:auto;"></div>
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.SID" class="col-sm-2 col-form-label"><span class="text-danger">*</span> SID</label>
+            <div class="col-sm-10">
+                <input type="text" asp-for="Input.SID" class="form-control" required minlength="24" maxlength="24" placeholder="중복 시 등록이 불가합니다. 24자" />
+                <div class="text-muted form-text">
+                    YouTube 채널 고유 ID (예: UCxxxxxxxxxxxxxxxxxxxxxx)
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.Name" class="col-sm-2 col-form-label"><span class="text-danger">*</span> 이름</label>
+            <div class="col-sm-10">
+                <input type="text" asp-for="Input.Name" class="form-control" required maxlength="200" />
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.Handle" class="col-sm-2 col-form-label">핸들</label>
+            <div class="col-sm-10">
+                <div class="input-group">
+                    <span class="input-group-text">@@</span>
+                    <input type="text" asp-for="Input.Handle" class="form-control" maxlength="30" />
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.YouTubeUrl" class="col-sm-2 col-form-label"><span class="text-danger">*</span> YouTube 주소</label>
+            <div class="col-sm-10">
+                <input type="url" asp-for="Input.YouTubeUrl" class="form-control" required maxlength="255" />
+                <div class="text-muted form-text">
+                    YouTube 채널 주소 (예: https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxxxxx)
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.PlatformFeeRate" class="col-sm-2 col-form-label"><span class="text-danger">*</span> 수수료(%)</label>
+            <div class="col-sm-10">
+                <div class="row">
+                    <div class="col col-md-auto">
+                        <div class="input-group">
+                            <input type="number" asp-for="Input.PlatformFeeRate" class="form-control" required min="0" max="100" step="0.1" />
+                            <span class="input-group-text">%</span>
+                        </div>
+                    </div>
+                </div>
+                <span asp-validation-for="Input.PlatformFeeRate" class="text-danger"></span>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.IsVerified" class="col-sm-2 col-form-label">인증 여부</label>
+            <div class="col-sm-10 align-content-center">
+                <div class="form-check form-check-inline">
+                    <input type="checkbox" asp-for="Input.IsVerified" class="form-check-input" />
+                    <label class="form-check-label" asp-for="Input.IsVerified">인증합니다.</label>
+                </div>
+            </div>
+        </div>
+        <div class="row mb-2">
+            <label asp-for="Input.IsActive" class="col-sm-2 col-form-label">사용 여부</label>
+            <div class="col-sm-10 align-content-center">
+                <div class="form-check form-check-inline">
+                    <input type="checkbox" asp-for="Input.IsActive" class="form-check-input" />
+                    <label class="form-check-label" asp-for="Input.IsActive">사용합니다.</label>
+                </div>
+            </div>
+        </div>
+
+        <hr />
+
+        <div class="d-grid gap-2 text-center d-md-block">
+            <button type="submit" class="btn btn-success">저장</button>
+            <a href="/Channel/List" class="btn btn-secondary">취소</a>
+        </div>
+
+        <br />
+    </form>
+</div>
+
+@section Scripts {
+<script>
+$(function () {
+    let timer = null;
+    const $input = $('#memberSearchInput');
+    const $results = $('#memberSearchResults');
+    const $hidden = $('#memberIdHidden');
+    const $selected = $('#memberSelected');
+    const $badgeText = $('#memberBadgeText');
+    const $searchWrap = $('#memberSearchWrap');
+
+    function showSelected(id, email, name, sid) {
+        const display = id + ' (' + email + ')' + (name ? ' ' + name : '') + (sid ? ' ' + sid : '');
+        $badgeText.text(display);
+        $hidden.val(id);
+        $selected.show();
+        $searchWrap.hide();
+        $results.hide();
+    }
+
+    function clearSelected() {
+        $hidden.val('');
+        $selected.hide();
+        $searchWrap.show();
+        $input.val('').focus();
+    }
+
+    $('#memberRemoveBtn').on('click', function () {
+        clearSelected();
+    });
+
+    $input.on('input', function () {
+        clearTimeout(timer);
+        const val = $(this).val().trim();
+        if (val.length < 1) {
+            $results.hide().empty();
+            return;
+        }
+        timer = setTimeout(function () {
+            $.getJSON('/Channel/List/Write?handler=SearchMember&keyword=' + encodeURIComponent(val), function (data) {
+                $results.empty();
+                if (data.length === 0) {
+                    $results.append('<div class="list-group-item text-muted">검색 결과가 없습니다.</div>');
+                } else {
+                    $.each(data, function (i, m) {
+                        const text = m.id + ' (' + m.email + ')' + (m.name ? ' ' + m.name : '') + (m.sid ? ' ' + m.sid : '');
+                        $results.append(
+                            $('<a href="#" class="list-group-item list-group-item-action"></a>')
+                                .text(text)
+                                .on('click', function (e) {
+                                    e.preventDefault();
+                                    showSelected(m.id, m.email, m.name, m.sid);
+                                })
+                        );
+                    });
+                }
+                $results.show();
+            });
+        }, 300);
+    });
+
+    $(document).on('click', function (e) {
+        if (!$(e.target).closest('#memberSearchWrap').length) {
+            $results.hide();
+        }
+    });
+
+    $input.on('focus', function () {
+        if ($results.children().length > 0) {
+            $results.show();
+        }
+    });
+
+    // 폼 제출 시 회원 선택 여부 확인
+    $('#fAdminWrite').on('submit', function (e) {
+        if (!$hidden.val() || $hidden.val() === '0') {
+            e.preventDefault();
+            alert('회원(소유자)을 선택해주세요.');
+            $input.focus();
+        }
+    });
+});
+</script>
+}

+ 126 - 0
Admin/Pages/Channel/List/Write.cshtml.cs

@@ -0,0 +1,126 @@
+using Application.Abstractions.Data;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace Admin.Pages.Channel.List
+{
+    public class WriteModel(IMediator mediator, IAppDbContext db) : PageModel
+    {
+        public string QueryString { get; private set; } = "";
+
+        [BindProperty]
+        public InputModel Input { get; set; } = new();
+
+        public sealed class InputModel
+        {
+            [DisplayName("회원 ID")]
+            [Required(ErrorMessage = "{0}은 필수입니다.")]
+            public int MemberID { get; set; }
+
+            [DisplayName("SID")]
+            [Required(ErrorMessage = "{0}은 필수입니다.")]
+            [StringLength(24, MinimumLength = 24, ErrorMessage = "{0}은 {1}자여야 합니다.")]
+            public string SID { get; set; } = default!;
+
+            [DisplayName("이름")]
+            [Required(ErrorMessage = "{0}은 필수입니다.")]
+            [StringLength(200, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
+            public string Name { get; set; } = default!;
+
+            [DisplayName("핸들")]
+            [StringLength(30, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
+            public string? Handle { get; set; }
+
+            [DisplayName("YouTube 주소")]
+            [Required(ErrorMessage = "{0}은 필수입니다.")]
+            [StringLength(255, ErrorMessage = "{0}은 {1}자 이내로 입력하세요.")]
+            public string YouTubeUrl { get; set; } = default!;
+
+            [DisplayName("수수료(%)")]
+            [Range(0, 100, ErrorMessage = "{0}은 {1}~{2} 사이여야 합니다.")]
+            public decimal PlatformFeeRate { get; set; } = 0;
+
+            [DisplayName("인증 여부")]
+            public bool IsVerified { get; set; } = false;
+
+            [DisplayName("사용 여부")]
+            public bool IsActive { get; set; } = false;
+        }
+
+        public Task OnGetAsync(CancellationToken _)
+        {
+            QueryString = HttpContext.Request.QueryString.HasValue ? HttpContext.Request.QueryString.Value!.TrimStart('?') : "";
+
+            return Task.CompletedTask;
+        }
+
+        public async Task<IActionResult> OnGetSearchMemberAsync(string keyword, CancellationToken ct)
+        {
+            if (string.IsNullOrWhiteSpace(keyword) || keyword.Trim().Length < 1)
+            {
+                return new JsonResult(Array.Empty<object>());
+            }
+
+            var kw = keyword.Trim();
+
+            var isNumeric = int.TryParse(kw, out var numericId);
+
+            var list = await db.Member
+                .AsNoTracking()
+                .Where(x =>
+                    (isNumeric && x.ID == numericId) ||
+                    x.SID.Contains(kw) ||
+                    x.Email.Contains(kw) ||
+                    (x.Name != null && x.Name.Contains(kw))
+                )
+                .OrderByDescending(x => x.ID)
+                .Take(10)
+                .Select(x => new
+                {
+                    id = x.ID,
+                    email = x.Email,
+                    name = x.Name,
+                    sid = x.SID
+                })
+                .ToListAsync(ct);
+
+            return new JsonResult(list);
+        }
+
+        public async Task<IActionResult> OnPostAsync(CancellationToken ct)
+        {
+            try
+            {
+                if (!ModelState.IsValid)
+                {
+                    throw new Exception("유효성 검사에 실패했습니다.");
+                }
+
+                await mediator.Send(new CreateChannel.Command(
+                    Input.MemberID,
+                    Input.SID,
+                    Input.Name,
+                    Input.Handle,
+                    Input.YouTubeUrl,
+                    Input.PlatformFeeRate,
+                    Input.IsVerified,
+                    Input.IsActive
+                ), ct);
+
+                TempData["SuccessMessage"] = "채널이 등록되었습니다.";
+
+                return RedirectToPage("/Channel/List/Index");
+            }
+            catch (Exception e)
+            {
+                TempData["ErrorMessages"] = e.Message;
+
+                return Page();
+            }
+        }
+    }
+}

+ 193 - 0
Admin/Pages/Director/AccessLog/Index.cshtml

@@ -0,0 +1,193 @@
+@page
+@model Admin.Pages.Director.AccessLog.IndexModel
+@{
+    ViewData["Title"] = "관리자 접근 기록";
+}
+
+<div class="container-fluid">
+    <h3>@ViewData["Title"]</h3>
+    <hr />
+
+    <partial name="_StatusMessage" />
+
+    <form id="fAdminSearch" method="get" accept-charset="utf-8" autocomplete="off">
+        <input type="hidden" name="pageNum" value="@Model.Query.PageNum" />
+
+        <div class="row g-2 mb-2">
+            <div class="col-12 col-lg-auto">
+                <div class="row g-2">
+                    <div class="col-6 col-sm-auto">
+                        <select name="search" class="form-select">
+                            <option value="1" selected="@(Model.Query.Search == 1)">계정</option>
+                            <option value="2" selected="@(Model.Query.Search == 2)">경로</option>
+                            <option value="3" selected="@(Model.Query.Search == 3)">IP</option>
+                            <option value="4" selected="@(Model.Query.Search == 4)">메뉴명</option>
+                        </select>
+                    </div>
+                    <div class="col-6 col-sm-auto">
+                        <select name="method" class="form-select">
+                            <option value="">전체</option>
+                            <option value="GET" selected="@(Model.Query.Method == "GET")">GET</option>
+                            <option value="POST" selected="@(Model.Query.Method == "POST")">POST</option>
+                        </select>
+                    </div>
+                    <div class="col-12 col-sm col-md col-lg-auto">
+                        <input type="search" name="keyword" class="form-control" maxlength="100" value="@Model.Query.Keyword" placeholder="검색어" />
+                    </div>
+                </div>
+            </div>
+            <div class="col-12 col-sm">
+                <div class="row g-2">
+                    <div class="col-12 col-md-auto">
+                        <div class="row row-cols-2 g-2">
+                            <div class="col">
+                                <input type="datetime-local" name="startAt" class="form-control" value="@Model.Query.StartAt" />
+                            </div>
+                            <div class="col">
+                                <input type="datetime-local" name="endAt" class="form-control" value="@Model.Query.EndAt" />
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col col-md-auto text-center">
+                        <button type="submit" class="btn btn-primary w-100" form="fAdminSearch">검색</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+
+    <hr/>
+
+    <div class="row g-2">
+        <div class="col align-self-end">
+            Total : @Model.Total.ToString("N0")
+        </div>
+        <div class="col-auto">
+            <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
+                <option value="20" selected="@(Model.Query.PerPage == 20)">20</option>
+                <option value="50" selected="@(Model.Query.PerPage == 50)">50</option>
+                <option value="100" selected="@(Model.Query.PerPage == 100)">100</option>
+            </select>
+        </div>
+        <div class="col-auto">
+            <button type="button" id="btnListDelete" class="btn btn-danger" form="fAdminList" disabled>삭제</button>
+        </div>
+    </div>
+
+    <form id="fAdminList" method="post" accept-charset="utf-8" asp-page-handler="Delete">
+        @Html.AntiForgeryToken()
+        <input type="hidden" name="pageNum" value="@Model.Query.PageNum" />
+        <input type="hidden" name="perPage" value="@Model.Query.PerPage" />
+        <input type="hidden" name="search" value="@Model.Query.Search" />
+        <input type="hidden" name="keyword" value="@Model.Query.Keyword" />
+        <input type="hidden" name="method" value="@Model.Query.Method" />
+        <input type="hidden" name="startAt" value="@Model.Query.StartAt" />
+        <input type="hidden" name="endAt" value="@Model.Query.EndAt" />
+    </form>
+
+    <div class="table-responsive">
+        <table class="table table-striped table-bordered table-hover mt-3">
+            <thead>
+                <tr>
+                    <th>
+                        <div class="form-check form-check-inline">
+                            <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
+                            <label for="checkedAll">ID</label>
+                        </div>
+                    </th>
+                    <th>계정</th>
+                    <th>Method</th>
+                    <th>경로</th>
+                    <th>상태</th>
+                    <th>처리(ms)</th>
+                    <th>메뉴</th>
+                    <th>IP</th>
+                    <th>등록일시</th>
+                    <th>비고</th>
+                </tr>
+            </thead>
+            <tbody>
+                @if (Model.List == null || Model.List.Count <= 0)
+                {
+                    <tr>
+                        <td colspan="10">No Data.</td>
+                    </tr>
+                }
+                else
+                {
+                    @foreach (var row in Model.List)
+                    {
+                        <tr>
+                            <td>
+                                <div class="form-check form-check-inline">
+                                    <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
+                                    <label for="ids_@row.ID">@row.ID</label>
+                                </div>
+                            </td>
+                            <td>@row.UserID</td>
+                            <td>
+                                @if (row.Method == "GET")
+                                {
+                                    <span class="badge bg-success">GET</span>
+                                }
+                                else if (row.Method == "POST")
+                                {
+                                    <span class="badge bg-warning text-dark">POST</span>
+                                }
+                                else
+                                {
+                                    <span class="badge bg-secondary">@row.Method</span>
+                                }
+                            </td>
+                            <td>
+                                <small>@row.Path</small>
+                                @if (!string.IsNullOrEmpty(row.QueryString))
+                                {
+                                    <small class="text-muted">@row.QueryString</small>
+                                }
+                            </td>
+                            <td>
+                                @if (row.StatusCode >= 200 && row.StatusCode < 300)
+                                {
+                                    <span class="badge bg-success">@row.StatusCode</span>
+                                }
+                                else if (row.StatusCode >= 300 && row.StatusCode < 400)
+                                {
+                                    <span class="badge bg-info">@row.StatusCode</span>
+                                }
+                                else if (row.StatusCode >= 400)
+                                {
+                                    <span class="badge bg-danger">@row.StatusCode</span>
+                                }
+                                else
+                                {
+                                    <span>@row.StatusCode</span>
+                                }
+                            </td>
+                            <td>@row.ElapsedMs.ToString("N0")</td>
+                            <td>@row.MenuName</td>
+                            <td>@row.IpAddress</td>
+                            <td>@row.CreatedAt</td>
+                            <td>
+                                <button type="button" class="btn btn-sm btn-outline-danger btn-row-delete" data-id="@row.ID">삭제</button>
+                            </td>
+                        </tr>
+                    }
+                }
+            </tbody>
+        </table>
+
+        <partial name="_Pagination" model="Model.Pagination" />
+    </div>
+</div>
+
+@section Scripts {
+    <script>
+        let searchForm = document.getElementById("fAdminSearch");
+
+        $(document).on("change", "#perPage", function () {
+            searchForm.elements["pageNum"].value = 1;
+            searchForm.submit();
+        });
+    </script>
+}

+ 119 - 0
Admin/Pages/Director/AccessLog/Index.cshtml.cs

@@ -0,0 +1,119 @@
+using SharedKernel.Helpers;
+using SharedKernel.Extensions;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace Admin.Pages.Director.AccessLog;
+
+public class IndexModel(IMediator mediator) : PageModel
+{
+    [BindProperty(SupportsGet = true)]
+    public QueryParams Query { get; set; } = new();
+
+    public sealed class QueryParams
+    {
+        [Range(1, int.MaxValue)]
+        [DisplayName("페이지 번호")]
+        public int PageNum { get; set; } = 1;
+
+        [Range(1, 100)]
+        [DisplayName("페이지 목록 수")]
+        public ushort PerPage { get; set; } = 20;
+
+        [DisplayName("검색 조건")]
+        [Range(1, 4, ErrorMessage = "{0}이(가) 올바르지 않습니다.")]
+        public int? Search { get; set; }
+
+        [DisplayName("검색어")]
+        [MaxLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
+        public string? Keyword { get; set; }
+
+        [DisplayName("Method")]
+        public string? Method { get; set; }
+
+        [DisplayName("시작일")]
+        public string? StartAt { get; set; }
+
+        [DisplayName("종료일")]
+        public string? EndAt { get; set; }
+    }
+
+    public int Total { get; set; } = 0;
+
+    public List<(
+        int Num,
+        long ID,
+        string UserID,
+        string? UserName,
+        string Method,
+        string Path,
+        string? QueryString,
+        int StatusCode,
+        long ElapsedMs,
+        string? MenuName,
+        string? IpAddress,
+        string CreatedAt
+    )> List { get; set; } = [];
+
+    public Pagination? Pagination { get; set; }
+
+    public async Task OnGetAsync(CancellationToken ct)
+    {
+        if (!ModelState.IsValid)
+        {
+            return;
+        }
+
+        var result = await mediator.Send(new SearchAdminAccessLogs.Query(
+            Query.PageNum,
+            Query.PerPage,
+            Query.Search,
+            Query.Keyword,
+            Query.Method,
+            Query.StartAt,
+            Query.EndAt
+        ), ct);
+
+        Total = result.Total;
+        List = [..result.List.Select(c => (
+            c.Num,
+            c.ID,
+            c.UserID,
+            c.UserName,
+            c.Method,
+            c.Path,
+            c.QueryString,
+            c.StatusCode,
+            c.ElapsedMs,
+            c.MenuName,
+            c.IpAddress,
+            c.CreatedAt.GetDateAt()
+        ))];
+
+        Pagination = new Pagination(result.Total, Query.PageNum, Query.PerPage);
+    }
+
+    public async Task<IActionResult> OnPostDeleteAsync(long[] ids, CancellationToken ct)
+    {
+        try
+        {
+            if (ids.Length == 0)
+            {
+                throw new Exception("삭제할 항목을 선택해주세요.");
+            }
+
+            await mediator.Send(new DeleteAdminAccessLog.Command(ids), ct);
+
+            TempData["SuccessMessage"] = $"{ids.Length}건이 삭제되었습니다.";
+        }
+        catch (Exception e)
+        {
+            TempData["ErrorMessages"] = e.Message;
+        }
+
+        return RedirectToPage(Query);
+    }
+}

+ 165 - 0
Admin/Pages/Director/LoginLog/Index.cshtml

@@ -0,0 +1,165 @@
+@page
+@model Admin.Pages.Director.LoginLog.IndexModel
+@{
+    ViewData["Title"] = "관리자 로그인 내역";
+}
+
+<div class="container-fluid">
+    <h3>@ViewData["Title"]</h3>
+    <hr />
+
+    <partial name="_StatusMessage" />
+
+    <form id="fAdminSearch" method="get" accept-charset="utf-8" autocomplete="off">
+        <input type="hidden" name="pageNum" value="@Model.Query.PageNum" />
+
+        <div class="row g-2 mb-2">
+            <div class="col-12 col-lg-auto">
+                <div class="row g-2">
+                    <div class="col-6 col-sm-auto">
+                        <select name="search" class="form-select">
+                            <option value="1" selected="@(Model.Query.Search == 1)">계정</option>
+                            <option value="2" selected="@(Model.Query.Search == 2)">IP</option>
+                        </select>
+                    </div>
+                    <div class="col-6 col-sm-auto">
+                        <select name="success" class="form-select">
+                            <option value="">전체</option>
+                            <option value="true" selected="@(Model.Query.Success == true)">성공</option>
+                            <option value="false" selected="@(Model.Query.Success == false)">실패</option>
+                        </select>
+                    </div>
+                    <div class="col-12 col-sm col-md col-lg-auto">
+                        <input type="search" name="keyword" class="form-control" maxlength="100" value="@Model.Query.Keyword" placeholder="검색어" />
+                    </div>
+                </div>
+            </div>
+            <div class="col-12 col-sm">
+                <div class="row g-2">
+                    <div class="col-12 col-md-auto">
+                        <div class="row row-cols-2 g-2">
+                            <div class="col">
+                                <input type="datetime-local" name="startAt" class="form-control" value="@Model.Query.StartAt" />
+                            </div>
+                            <div class="col">
+                                <input type="datetime-local" name="endAt" class="form-control" value="@Model.Query.EndAt" />
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col col-md-auto text-center">
+                        <button type="submit" class="btn btn-primary w-100" form="fAdminSearch">검색</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+
+    <hr/>
+
+    <div class="row g-2">
+        <div class="col align-self-end">
+            Total : @Model.Total.ToString("N0")
+        </div>
+        <div class="col-auto">
+            <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
+                <option value="10" selected="@(Model.Query.PerPage == 10)">10</option>
+                <option value="20" selected="@(Model.Query.PerPage == 20)">20</option>
+                <option value="50" selected="@(Model.Query.PerPage == 50)">50</option>
+                <option value="100" selected="@(Model.Query.PerPage == 100)">100</option>
+            </select>
+        </div>
+        <div class="col-auto">
+            <button type="button" id="btnListDelete" class="btn btn-danger" form="fAdminList" disabled>삭제</button>
+        </div>
+    </div>
+
+    <form id="fAdminList" method="post" accept-charset="utf-8" asp-page-handler="Delete">
+        @Html.AntiForgeryToken()
+        <input type="hidden" name="pageNum" value="@Model.Query.PageNum" />
+        <input type="hidden" name="perPage" value="@Model.Query.PerPage" />
+        <input type="hidden" name="search" value="@Model.Query.Search" />
+        <input type="hidden" name="keyword" value="@Model.Query.Keyword" />
+        <input type="hidden" name="success" value="@Model.Query.Success" />
+        <input type="hidden" name="startAt" value="@Model.Query.StartAt" />
+        <input type="hidden" name="endAt" value="@Model.Query.EndAt" />
+    </form>
+
+    <div class="table-responsive">
+        <table class="table table-striped table-bordered table-hover mt-3">
+            <thead>
+                <tr>
+                    <th>
+                        <div class="form-check form-check-inline">
+                            <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
+                            <label for="checkedAll">ID</label>
+                        </div>
+                    </th>
+                    <th>성공</th>
+                    <th>계정</th>
+                    <th>사유</th>
+                    <th>IP</th>
+                    <th>브라우저</th>
+                    <th>장치</th>
+                    <th>OS</th>
+                    <th>등록일시</th>
+                    <th>비고</th>
+                </tr>
+            </thead>
+            <tbody>
+                @if (Model.List == null || Model.List.Count <= 0)
+                {
+                    <tr>
+                        <td colspan="10">No Data.</td>
+                    </tr>
+                }
+                else
+                {
+                    @foreach (var row in Model.List)
+                    {
+                        <tr>
+                            <td>
+                                <div class="form-check form-check-inline">
+                                    <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
+                                    <label for="ids_@row.ID">@row.ID</label>
+                                </div>
+                            </td>
+                            <td>
+                                @if (row.Success)
+                                {
+                                    <span class="badge bg-success">성공</span>
+                                }
+                                else
+                                {
+                                    <span class="badge bg-danger">실패</span>
+                                }
+                            </td>
+                            <td>@row.Account</td>
+                            <td>@row.Reason</td>
+                            <td>@row.IpAddress</td>
+                            <td>@row.Browser</td>
+                            <td>@row.Device</td>
+                            <td>@row.OS</td>
+                            <td>@row.CreatedAt</td>
+                            <td>
+                                <button type="button" class="btn btn-sm btn-outline-danger btn-row-delete" data-id="@row.ID">삭제</button>
+                            </td>
+                        </tr>
+                    }
+                }
+            </tbody>
+        </table>
+
+        <partial name="_Pagination" model="Model.Pagination" />
+    </div>
+</div>
+
+@section Scripts {
+    <script>
+        let searchForm = document.getElementById("fAdminSearch");
+
+        $(document).on("change", "#perPage", function () {
+            searchForm.elements["pageNum"].value = 1;
+            searchForm.submit();
+        });
+    </script>
+}

+ 115 - 0
Admin/Pages/Director/LoginLog/Index.cshtml.cs

@@ -0,0 +1,115 @@
+using SharedKernel.Helpers;
+using SharedKernel.Extensions;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace Admin.Pages.Director.LoginLog;
+
+public class IndexModel(IMediator mediator) : PageModel
+{
+    [BindProperty(SupportsGet = true)]
+    public QueryParams Query { get; set; } = new();
+
+    public sealed class QueryParams
+    {
+        [Range(1, int.MaxValue)]
+        [DisplayName("페이지 번호")]
+        public int PageNum { get; set; } = 1;
+
+        [Range(1, 100)]
+        [DisplayName("페이지 목록 수")]
+        public ushort PerPage { get; set; } = 10;
+
+        [DisplayName("검색 조건")]
+        [Range(1, 2, ErrorMessage = "{0}이(가) 올바르지 않습니다.")]
+        public int? Search { get; set; }
+
+        [DisplayName("검색어")]
+        [MaxLength(255, ErrorMessage = "{0}은(는) {1}자 이하로 입력하세요.")]
+        public string? Keyword { get; set; }
+
+        [DisplayName("성공 여부")]
+        public bool? Success { get; set; }
+
+        [DisplayName("시작일")]
+        public string? StartAt { get; set; }
+
+        [DisplayName("종료일")]
+        public string? EndAt { get; set; }
+    }
+
+    public int Total { get; set; } = 0;
+
+    public List<(
+        int Num,
+        int ID,
+        string Account,
+        bool Success,
+        string? Reason,
+        string? IpAddress,
+        string? Browser,
+        string? OS,
+        string? Device,
+        string CreatedAt
+    )> List { get; set; } = [];
+
+    public Pagination? Pagination { get; set; }
+
+    public async Task OnGetAsync(CancellationToken ct)
+    {
+        if (!ModelState.IsValid)
+        {
+            return;
+        }
+
+        var result = await mediator.Send(new SearchAdminLoginLogs.Query(
+            Query.PageNum,
+            Query.PerPage,
+            Query.Search,
+            Query.Keyword,
+            Query.Success,
+            Query.StartAt,
+            Query.EndAt
+        ), ct);
+
+        Total = result.Total;
+        List = [..result.List.Select(c => (
+            c.Num,
+            c.ID,
+            c.Account,
+            c.Success,
+            c.Reason ?? "-",
+            c.IpAddress,
+            c.Browser ?? "-",
+            c.OS ?? "-",
+            c.Device ?? "-",
+            c.CreatedAt.GetDateAt()
+        ))];
+
+        Pagination = new Pagination(result.Total, Query.PageNum, Query.PerPage);
+    }
+
+    public async Task<IActionResult> OnPostDeleteAsync(int[] ids, CancellationToken ct)
+    {
+        try
+        {
+            if (ids.Length == 0)
+            {
+                throw new Exception("삭제할 항목을 선택해주세요.");
+            }
+
+            await mediator.Send(new DeleteAdminLoginLog.Command(ids), ct);
+
+            TempData["SuccessMessage"] = $"{ids.Length}건이 삭제되었습니다.";
+        }
+        catch (Exception e)
+        {
+            TempData["ErrorMessages"] = e.Message;
+        }
+
+        return RedirectToPage(Query);
+    }
+}

+ 8 - 1
Admin/Pages/Director/User/Index.cshtml

@@ -1,7 +1,7 @@
 @page
 @model Admin.Pages.Director.User.IndexModel
 @{
-    ViewData["Title"] = "관리자";
+    ViewData["Title"] = "관리자 정보";
 }
 
 <div class="container">
@@ -22,6 +22,13 @@
 
     <div class="table-responsive">
         <table class="table table-striped table-bordered table-hover mt-3">
+            <colgroup>
+                <col width="30%"/>
+                <col />
+                <col />
+                <col />
+                <col />
+            </colgroup>
             <thead>
                 <tr>
                     <th>ID</th>

+ 2 - 6
Admin/Pages/Director/User/Roles.cshtml

@@ -50,18 +50,14 @@
         <div class="text-center">
             @if (Model.Input.Roles != null && Model.Input.Roles.Count >= 0)
             {
-                <button type="submit" class="btn btn-sm btn-success">저장</button>
+                <button type="submit" class="btn btn-success">저장</button>
             }
-            <a asp-page="/Director/User/Index" class="btn btn-sm btn-secondary">취소</a>
+            <a asp-page="/Director/User/Index" class="btn btn-secondary">취소</a>
         </div>
     </form>
 </div>
 
 @section Scripts {
-    @{
-
-    }
-
     <script>
         // checkbox 모두 선택/해제
         document.getElementById("allChecked").addEventListener("change", function () {

+ 3 - 3
Admin/Pages/Document/Index.cshtml

@@ -12,7 +12,7 @@
 
     <div class="row g-2 align-items-end">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -43,7 +43,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -70,7 +70,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 1 - 1
Admin/Pages/Faq/Category.cshtml

@@ -9,7 +9,7 @@
     <hr />
 
     <partial name="_StatusMessage" />
-    <partial name="_navTabs" />
+    <partial name="_NavTabs" />
 
     <div class="row g-2 align-items-end mt-2">
         <div class="col">

+ 4 - 4
Admin/Pages/Faq/List/Index.cshtml

@@ -9,7 +9,7 @@
     <hr />
 
     <partial name="_StatusMessage" />
-    <partial name="_navTabs" />
+    <partial name="_NavTabs" />
 
     <div class="row g-2 mb-2 mb-sm-0 mt-2">
         <div class="col-auto">
@@ -31,7 +31,7 @@
 
     <div class="row g-2 align-items-end">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -62,7 +62,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -89,7 +89,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 1 - 1
Admin/Pages/Forum/Posts/List/Edit.cshtml

@@ -74,7 +74,7 @@
             <a href="@(Model.ReturnUrl ?? "/Forum/Posts/List")" class="btn btn-secondary">취소</a>
             <button type="submit"
                     class="btn btn-danger"
-                    asp-page-handler="Delete"
+                    formaction="?handler=Delete"
                     formnovalidate
                     onclick="return confirm('삭제 하시겠습니까? 삭제된 게시물은 복구가 불가능합니다.');">
                 삭제

+ 1 - 1
Admin/Pages/Forum/Posts/List/Edit.cshtml.cs

@@ -1,7 +1,7 @@
+using SharedKernel.Extensions;
 using MediatR;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc.RazorPages;
-using SharedKernel.Extensions;
 using System.ComponentModel;
 using System.ComponentModel.DataAnnotations;
 

+ 2 - 2
Admin/Pages/Member/Grade/Index.cshtml

@@ -12,7 +12,7 @@
 
     <div class="row g-2 align-items-end">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col text-end">
             <a class="btn btn-success" href="/Member/Grade/Write">추가</a>
@@ -20,7 +20,7 @@
     </div>
 
     <div class="table-responsive">
-        <table class="table table-bordered mt-3">
+        <table class="table table-striped table-bordered mt-3">
             <colgroup>
                 <col width="*"/>
                 <col width="140px"/>

+ 3 - 3
Admin/Pages/Member/List/Index.cshtml

@@ -90,7 +90,7 @@
 
     <div class="row g-2">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -143,7 +143,7 @@
                 <tr>
                     <th rowspan="2">ID</th>
                     <th colspan="2">
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">이메일</label>
                         </div>
@@ -182,7 +182,7 @@
                         <tr>
                             <td rowspan="2">@row.ID</td>
                             <td colspan="2">
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="chk_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="chk_@row.ID" class="form-check-inline">@row.Email</label>
                                 </div>

+ 3 - 3
Admin/Pages/Member/Log/Email.cshtml

@@ -57,7 +57,7 @@
 
     <div class="row g-2 align-items-end">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col text-end">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -92,7 +92,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -117,7 +117,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 3 - 3
Admin/Pages/Member/Log/Intro.cshtml

@@ -57,7 +57,7 @@
 
     <div class="row g-2">
         <div class="col align-self-end">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -94,7 +94,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -119,7 +119,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 3 - 3
Admin/Pages/Member/Log/Login/Index.cshtml

@@ -55,7 +55,7 @@
 
     <div class="row g-2">
         <div class="col align-self-end">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -84,7 +84,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -113,7 +113,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 3 - 3
Admin/Pages/Member/Log/Name.cshtml

@@ -57,7 +57,7 @@
 
     <div class="row g-2">
         <div class="col align-self-end">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -94,7 +94,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -119,7 +119,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 3 - 3
Admin/Pages/Member/Log/Summary.cshtml

@@ -57,7 +57,7 @@
 
     <div class="row g-2">
         <div class="col align-self-end">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -89,7 +89,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -114,7 +114,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 2 - 2
Admin/Pages/Member/Wallet/List/Index.cshtml

@@ -52,7 +52,7 @@
 
     <div class="row g-2 align-items-end">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col text-end">
             <select name="per_page" id="perPage" class="form-select w-auto d-inline-block" form="fAdminList">
@@ -65,7 +65,7 @@
     </div>
 
     <div class="table-responsive">
-        <table class="table table-bordered table-hover mt-3">
+        <table class="table table-striped table-bordered table-hover mt-3">
             <colgroup>
                 <col style="width: 5%;" />
                 <col />

+ 5 - 5
Admin/Pages/Member/Wallet/Transactions/Index.cshtml

@@ -54,7 +54,7 @@
 
     <div class="row g-2 align-items-end mb-3">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -109,7 +109,7 @@
     </form>
 
     <div class="table-responsive">
-        <table class="table table-bordered table-hover mt-3">
+        <table class="table table-striped table-bordered table-hover mt-3">
             <colgroup>
                 <col style="width: 5%;" />
                 <col />
@@ -125,7 +125,7 @@
                 <tr>
                     <th>No</th>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">거래 ID</label>
                         </div>
@@ -155,9 +155,9 @@
                         <tr>
                             <td>@row.Num</td>
                             <td>
-                                <div>
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="checkList[]" id="chk_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
-                                    <label for="chk_@row.ID" class="form-check-inline">@row.ID</label>
+                                    <label for="chk_@row.ID" class="form-check-label">@row.ID</label>
                                 </div>
                             </td>
                             <td>[@row.MemberID] @row.MemberEmail, @row.MemberName</td>

+ 0 - 20
Admin/Pages/Member/_navTabs.cshtml

@@ -1,20 +0,0 @@
-@{
-    var basePath = "/Member/Log/";
-}
-<ul class="nav nav-tabs">
-    <li class="nav-item">
-        <a class="nav-link @Html.IsActive(basePath + "Login")" asp-page="@(basePath + "Login")">로그인 내역</a>
-    </li>
-    <li class="nav-item">
-        <a class="nav-link @Html.IsActive(basePath + "Email")" asp-page="@(basePath + "Email")">이메일 변경</a>
-    </li>
-    <li class="nav-item">
-        <a class="nav-link @Html.IsActive(basePath + "Name")" asp-page="@(basePath + "Name")">별명 변경</a>
-    </li>
-    <li class="nav-item">
-        <a class="nav-link @Html.IsActive(basePath + "Summary")" asp-page="@(basePath + "Summary")">한마디 변경</a>
-    </li>
-    <li class="nav-item">
-        <a class="nav-link @Html.IsActive(basePath + "Intro")" asp-page="@(basePath + "Intro")">자기소개 변경</a>
-    </li>
-</ul>

+ 3 - 3
Admin/Pages/Popup/Index.cshtml

@@ -12,7 +12,7 @@
 
     <div class="row g-2 align-items-end">
         <div class="col">
-            Total : @Model.Total
+            Total : @Model.Total.ToString("N0")
         </div>
         <div class="col-auto">
             <select name="perPage" id="perPage" class="form-select w-auto d-inline-block" form="fAdminSearch">
@@ -44,7 +44,7 @@
             <thead>
                 <tr>
                     <th>
-                        <div class="form-check-inline">
+                        <div class="form-check form-check-inline">
                             <input type="checkbox" id="checkedAll" class="form-check-input" value="1" form="fAdminList" />
                             <label for="checkedAll">ID</label>
                         </div>
@@ -72,7 +72,7 @@
                     {
                         <tr>
                             <td>
-                                <div class="form-check-inline">
+                                <div class="form-check form-check-inline">
                                     <input type="checkbox" name="ids[]" id="ids_@row.ID" class="form-check-input list-check-box" value="@row.ID" form="fAdminList" />
                                     <label for="ids_@row.ID">@row.ID</label>
                                 </div>

+ 9 - 3
Admin/Pages/Shared/_MenuItem.cshtml

@@ -10,10 +10,16 @@
     bool isActive = false;
     bool isParentActive = false;
 
-    // "게시판 관리" 특별 처리
-    if (Model.Name.Equals("게시판 관리", StringComparison.OrdinalIgnoreCase))
+    // 특별 처리: navtabs가 있는 메뉴 (하위 경로가 메뉴 Path와 다른 경우)
+    var multiPathMenus = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
+    {
+        { "게시판 관리", new[] { "/forum/board/meta", "/forum/board/prefix", "/forum/board/manager", "/forum/board/list" } },
+        { "FAQ 관리", new[] { "/faq/list", "/faq/category" } },
+        { "배너 관리", new[] { "/banner/list", "/banner/position" } }
+    };
+
+    if (multiPathMenus.TryGetValue(Model.Name, out var activePrefixes))
     {
-        string[] activePrefixes = { "/forum/board/meta", "/forum/board/prefix", "/forum/board/manager", "/forum/board/list" };
         isActive = currentPath != null && activePrefixes.Any(prefix =>
             currentPath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
         );

+ 2 - 0
Admin/Pages/Shared/_Pagination.cshtml

@@ -49,4 +49,6 @@
         }
     </ul>
 </nav>
+<br/>
+<br/>
 }

+ 6 - 6
Admin/Program.cs

@@ -1,6 +1,7 @@
 using SharedKernel;
 using Admin.Extensions;
 using Admin.Pages.Shared.Layout;
+using Admin.Middlewares;
 using Application;
 using Infrastructure;
 using Microsoft.AspNetCore.Identity;
@@ -11,9 +12,7 @@ var settings = builder.Configuration.Get<AppSettings>()!;
 
 Console.Title = settings.App.Name;
 Console.WriteLine($"ENV={builder.Environment.EnvironmentName}");
-Console.WriteLine(TimeZoneInfo.Local.Id);
-Console.WriteLine(DateTime.Now);
-Console.WriteLine(DateTime.UtcNow);
+Console.WriteLine($"현재 시간: {DateTime.Now} / {TimeZoneInfo.Local.Id}");
 
 // Add services to the container.
 builder.Services.AddRazorPages(options =>
@@ -26,9 +25,7 @@ builder.Services.AddRazorPages(options =>
 builder.Services.Configure<AppSettings>(builder.Configuration);
 
 // DB 연결
-builder.Services
-    .AddApplication()
-    .AddAdminInfrastructure(builder.Configuration);
+builder.Services.AddApplication().AddAdminInfrastructure(builder.Configuration);
 
 // 관리자 레이아웃
 builder.Services.AddScoped<ILayoutDataProvider, LayoutDataProvider>();
@@ -83,6 +80,9 @@ app.UseRouting();
 app.UseAuthentication();
 app.UseAuthorization();
 
+// 관리자 접속 기록
+app.UseMiddleware<AdminAccessLogMiddleware>();
+
 app.MapStaticAssets();
 app.MapRazorPages().WithStaticAssets();
 

+ 16 - 1
Admin/using.cs

@@ -12,6 +12,14 @@ global using DeleteRole = Application.Features.Director.Role.Delete;
 global using GetRolePermissions = Application.Features.Director.Role.Permissions.Get;
 global using UpdateRolePermissions = Application.Features.Director.Role.Permissions.Update;
 
+// 관리자 로그인 내역
+global using SearchAdminLoginLogs = Application.Features.Director.LoginLog.Search;
+global using DeleteAdminLoginLog = Application.Features.Director.LoginLog.Delete;
+
+// 관리자 접근 기록
+global using SearchAdminAccessLogs = Application.Features.Director.AccessLog.Search;
+global using DeleteAdminAccessLog = Application.Features.Director.AccessLog.Delete;
+
 // 문서
 global using SearchDocuments = Application.Features.Document.Search;
 global using GetDocument = Application.Features.Document.Get;
@@ -174,4 +182,11 @@ global using DeletePostReport = Application.Features.Forum.PostReport.Delete;
 // 댓글 신고
 global using SearchCommentReports = Application.Features.Forum.CommentReport.Search;
 global using UpdateCommentReportStatus = Application.Features.Forum.CommentReport.UpdateStatus;
-global using DeleteCommentReport = Application.Features.Forum.CommentReport.Delete;
+global using DeleteCommentReport = Application.Features.Forum.CommentReport.Delete;
+
+// 채널 목록
+global using SearchChannels = Application.Features.Channel.List.Search;
+global using GetChannel = Application.Features.Channel.List.Get;
+global using CreateChannel = Application.Features.Channel.List.Create;
+global using UpdateChannel = Application.Features.Channel.List.Update;
+global using DeleteChannel = Application.Features.Channel.List.Delete;

+ 1 - 1
Admin/wwwroot/js/site.js

@@ -159,7 +159,7 @@ class ActionButtons {
             case "Update":
                 handler = "?handler=Update";
             default:
-                return "";
+                handler = null;
         };
 
         if (handler) {

+ 4 - 0
Application/Abstractions/Data/IAppDbContext.cs

@@ -9,12 +9,16 @@ using Domain.Entities.Wallets;
 using Domain.Entities.Forum.Boards;
 using Domain.Entities.Forum.Posts;
 using Domain.Entities.Forum.Comments;
+using Domain.Entities.Director;
 using Domain.Entities.Forum.Logs;
 
 namespace Application.Abstractions.Data
 {
     public interface IAppDbContext
     {
+        DbSet<AdminLoginLog> AdminLoginLog { get; set; }
+        DbSet<AdminAccessLog> AdminAccessLog { get; set; }
+
         DbSet<Config> Config { get; set;  }
         DbSet<Document> Document { get; set;  }
         DbSet<Popup> Popup { get; set;  }

+ 14 - 0
Application/Features/Channel/List/Create/Command.cs

@@ -0,0 +1,14 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Channel.List.Create;
+
+public sealed record Command(
+    int MemberID,
+    string SID,
+    string Name,
+    string? Handle,
+    string YouTubeUrl,
+    decimal PlatformFeeRate,
+    bool IsVerified,
+    bool IsActive
+) : ICommand<int>;

+ 38 - 0
Application/Features/Channel/List/Create/Handler.cs

@@ -0,0 +1,38 @@
+using Application.Abstractions.Data;
+using Application.Abstractions.Messaging;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Channel.List.Create;
+
+public sealed class Handler(IAppDbContext db) : ICommandHandler<Command, int>
+{
+    public async Task<int> Handle(Command request, CancellationToken ct)
+    {
+        var exists = await db.Channel.AnyAsync(c => c.SID == request.SID, ct);
+        if (exists)
+        {
+            throw new Exception("이미 등록된 채널 SID입니다.");
+        }
+
+        var channel = Domain.Entities.Members.Channel.Create(
+            request.MemberID,
+            request.SID,
+            request.Name,
+            request.YouTubeUrl
+        );
+
+        channel.Update(
+            request.Name,
+            request.Handle,
+            request.YouTubeUrl,
+            request.PlatformFeeRate,
+            request.IsVerified,
+            request.IsActive
+        );
+
+        await db.Channel.AddAsync(channel, ct);
+        await db.SaveChangesAsync(ct);
+
+        return channel.ID;
+    }
+}

+ 5 - 0
Application/Features/Channel/List/Delete/Command.cs

@@ -0,0 +1,5 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Channel.List.Delete;
+
+public sealed record Command(int[] IDs) : ICommand;

+ 27 - 0
Application/Features/Channel/List/Delete/Handler.cs

@@ -0,0 +1,27 @@
+using Application.Abstractions.Data;
+using Application.Abstractions.Messaging;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Channel.List.Delete;
+
+public sealed class Handler(IAppDbContext db) : ICommandHandler<Command>
+{
+    public async Task Handle(Command request, CancellationToken ct)
+    {
+        if (request.IDs is null || request.IDs.Length < 1)
+        {
+            return;
+        }
+
+        var list = await db.Channel.Where(c => request.IDs.Contains(c.ID)).ToListAsync(ct);
+
+        if (list.Count < 1)
+        {
+            return;
+        }
+
+        db.Channel.RemoveRange(list);
+
+        await db.SaveChangesAsync(ct);
+    }
+}

+ 34 - 0
Application/Features/Channel/List/Get/Handler.cs

@@ -0,0 +1,34 @@
+using Application.Abstractions.Data;
+using Application.Abstractions.Messaging;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Channel.List.Get;
+
+public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response?>
+{
+    public async Task<Response?> Handle(Query request, CancellationToken ct)
+    {
+        return await db.Channel
+            .AsNoTracking()
+            .Include(c => c.Member)
+            .Where(c => c.ID == request.ID)
+            .Select(c => new Response
+            {
+                ID = c.ID,
+                MemberID = c.MemberID,
+                MemberEmail = c.Member.Email,
+                MemberName = c.Member.Name,
+                MemberSID = c.Member.SID,
+                SID = c.SID,
+                Name = c.Name,
+                Handle = c.Handle,
+                YouTubeUrl = c.YouTubeUrl,
+                PlatformFeeRate = c.PlatformFeeRate,
+                IsVerified = c.IsVerified,
+                IsActive = c.IsActive,
+                UpdatedAt = c.UpdatedAt,
+                CreatedAt = c.CreatedAt
+            })
+            .FirstOrDefaultAsync(ct);
+    }
+}

+ 5 - 0
Application/Features/Channel/List/Get/Query.cs

@@ -0,0 +1,5 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Channel.List.Get;
+
+public sealed record Query(int ID) : IQuery<Response?>;

+ 19 - 0
Application/Features/Channel/List/Get/Response.cs

@@ -0,0 +1,19 @@
+namespace Application.Features.Channel.List.Get;
+
+public sealed class Response
+{
+    public int ID { get; init; }
+    public int MemberID { get; init; }
+    public string? MemberEmail { get; init; }
+    public string? MemberName { get; init; }
+    public string? MemberSID { get; init; }
+    public required string SID { get; init; }
+    public required string Name { get; init; }
+    public string? Handle { get; init; }
+    public required string YouTubeUrl { get; init; }
+    public decimal PlatformFeeRate { get; init; }
+    public bool IsVerified { get; init; }
+    public bool IsActive { get; init; }
+    public DateTime? UpdatedAt { get; init; }
+    public DateTime CreatedAt { get; init; }
+}

+ 91 - 0
Application/Features/Channel/List/Search/Handler.cs

@@ -0,0 +1,91 @@
+using Application.Abstractions.Data;
+using Application.Abstractions.Messaging;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Channel.List.Search;
+
+public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
+{
+    public async Task<Response> Handle(Query request, CancellationToken ct)
+    {
+        var query = db.Channel.AsNoTracking().Include(c => c.Member).AsQueryable();
+
+        if (!string.IsNullOrWhiteSpace(request.Keyword))
+        {
+            var kw = request.Keyword.Trim();
+            query = request.Search switch
+            {
+                1 => query.Where(c => c.Name.Contains(kw)),
+                2 => query.Where(c => c.SID.Contains(kw)),
+                3 => query.Where(c => c.Handle != null && c.Handle.Contains(kw)),
+                4 => query.Where(c => c.YouTubeUrl.Contains(kw)),
+                5 => query.Where(c => c.MemberID.ToString().Contains(kw)),
+                6 => query.Where(c => c.Member.Email.Contains(kw)),
+                7 => query.Where(c => c.Member.Name != null && c.Member.Name.Contains(kw)),
+                _ => query.Where(c => c.Name.Contains(kw))
+            };
+        }
+
+        if (request.IsVerified.HasValue)
+        {
+            query = query.Where(c => c.IsVerified == request.IsVerified.Value);
+        }
+
+        if (!string.IsNullOrWhiteSpace(request.StartAt) && DateTime.TryParse(request.StartAt, out var startDate))
+        {
+            query = query.Where(c => c.CreatedAt >= startDate);
+        }
+
+        if (!string.IsNullOrWhiteSpace(request.EndAt) && DateTime.TryParse(request.EndAt, out var endDate))
+        {
+            query = query.Where(c => c.CreatedAt <= endDate.AddDays(1));
+        }
+
+        query = query.OrderByDescending(c => c.ID);
+
+        var total = await query.CountAsync(ct);
+
+        var list = await query
+            .Skip((request.PageNum - 1) * request.PerPage)
+            .Take(request.PerPage)
+            .Select(c => new
+            {
+                c.ID,
+                c.SID,
+                c.Name,
+                c.Handle,
+                c.YouTubeUrl,
+                c.PlatformFeeRate,
+                c.IsVerified,
+                c.IsActive,
+                c.MemberID,
+                MemberName = c.Member.Name,
+                MemberEmail = c.Member.Email,
+                c.UpdatedAt,
+                c.CreatedAt
+            })
+            .ToListAsync(ct);
+
+        var startNum = total - ((request.PageNum - 1) * request.PerPage);
+
+        return new Response(
+            total,
+            [..list.Select((c, i) => new Response.Row(
+                Num: startNum - i,
+                c.ID,
+                c.SID,
+                c.Name,
+                c.Handle,
+                c.YouTubeUrl,
+                c.PlatformFeeRate,
+                c.IsVerified,
+                c.IsActive,
+                c.MemberID,
+                c.MemberName,
+                c.MemberEmail,
+                c.UpdatedAt,
+                c.CreatedAt
+            ))]
+        );
+    }
+}

+ 13 - 0
Application/Features/Channel/List/Search/Query.cs

@@ -0,0 +1,13 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Channel.List.Search;
+
+public sealed record Query(
+    int? Search,
+    string? Keyword,
+    bool? IsVerified,
+    string? StartAt,
+    string? EndAt,
+    int PageNum,
+    ushort PerPage
+) : IQuery<Response>;

+ 21 - 0
Application/Features/Channel/List/Search/Response.cs

@@ -0,0 +1,21 @@
+namespace Application.Features.Channel.List.Search;
+
+public sealed record Response(int Total, IReadOnlyList<Response.Row> List)
+{
+    public sealed record Row(
+        int Num,
+        int ID,
+        string SID,
+        string Name,
+        string? Handle,
+        string YouTubeUrl,
+        decimal PlatformFeeRate,
+        bool IsVerified,
+        bool IsActive,
+        int MemberID,
+        string? MemberName,
+        string? MemberEmail,
+        DateTime? UpdatedAt,
+        DateTime CreatedAt
+    );
+}

+ 13 - 0
Application/Features/Channel/List/Update/Command.cs

@@ -0,0 +1,13 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Channel.List.Update;
+
+public sealed record Command(
+    int ID,
+    string Name,
+    string? Handle,
+    string YouTubeUrl,
+    decimal PlatformFeeRate,
+    bool IsVerified,
+    bool IsActive
+) : ICommand;

+ 28 - 0
Application/Features/Channel/List/Update/Handler.cs

@@ -0,0 +1,28 @@
+using Application.Abstractions.Data;
+using Application.Abstractions.Messaging;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Channel.List.Update;
+
+public sealed class Handler(IAppDbContext db) : ICommandHandler<Command>
+{
+    public async Task Handle(Command request, CancellationToken ct)
+    {
+        var channel = await db.Channel.FirstOrDefaultAsync(c => c.ID == request.ID, ct);
+        if (channel is null)
+        {
+            throw new Exception("채널을 찾을 수 없습니다.");
+        }
+
+        channel.Update(
+            request.Name,
+            request.Handle,
+            request.YouTubeUrl,
+            request.PlatformFeeRate,
+            request.IsVerified,
+            request.IsActive
+        );
+
+        await db.SaveChangesAsync(ct);
+    }
+}

+ 5 - 0
Application/Features/Director/AccessLog/Delete/Command.cs

@@ -0,0 +1,5 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Director.AccessLog.Delete;
+
+public sealed record Command(long[] IDs) : ICommand;

+ 18 - 0
Application/Features/Director/AccessLog/Delete/Handler.cs

@@ -0,0 +1,18 @@
+using Application.Abstractions.Messaging;
+using Application.Abstractions.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Director.AccessLog.Delete;
+
+public sealed class Handler(IAppDbContext db) : ICommandHandler<Command>
+{
+    public async Task Handle(Command request, CancellationToken ct)
+    {
+        if (request.IDs is null || request.IDs.Length == 0)
+        {
+            return;
+        }
+
+        await db.AdminAccessLog.Where(x => request.IDs.Contains(x.ID)).ExecuteDeleteAsync(ct);
+    }
+}

+ 91 - 0
Application/Features/Director/AccessLog/Search/Handler.cs

@@ -0,0 +1,91 @@
+using Application.Abstractions.Messaging;
+using Application.Abstractions.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Director.AccessLog.Search;
+
+public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
+{
+    public async Task<Response> Handle(Query request, CancellationToken ct)
+    {
+        var query = db.AdminAccessLog.AsNoTracking().AsQueryable();
+
+        // 키워드 검색
+        if (!string.IsNullOrWhiteSpace(request.Keyword))
+        {
+            var kw = request.Keyword.Trim();
+            query = request.Search switch
+            {
+                1 => query.Where(x => x.UserID.Contains(kw)),
+                2 => query.Where(x => x.Path.Contains(kw)),
+                3 => query.Where(x => x.IpAddress != null && x.IpAddress.Contains(kw)),
+                4 => query.Where(x => x.MenuName != null && x.MenuName.Contains(kw)),
+                _ => query.Where(x => x.UserID.Contains(kw))
+            };
+        }
+
+        // Method 필터
+        if (!string.IsNullOrWhiteSpace(request.Method))
+        {
+            query = query.Where(x => x.Method == request.Method);
+        }
+
+        // 날짜 필터
+        if (!string.IsNullOrWhiteSpace(request.StartAt) && DateTime.TryParse(request.StartAt, out var startAt))
+        {
+            query = query.Where(x => x.CreatedAt >= startAt);
+        }
+
+        if (!string.IsNullOrWhiteSpace(request.EndAt) && DateTime.TryParse(request.EndAt, out var endAt))
+        {
+            query = query.Where(x => x.CreatedAt <= endAt);
+        }
+
+        var total = await query.CountAsync(ct);
+        var skip = (request.PageNum - 1) * request.PerPage;
+
+        var list = await query
+            .OrderByDescending(x => x.ID)
+            .Skip(skip)
+            .Take(request.PerPage)
+            .Select(x => new
+            {
+                x.ID,
+                x.UserID,
+                x.UserName,
+                x.Method,
+                x.Path,
+                x.QueryString,
+                x.StatusCode,
+                x.ElapsedMs,
+                x.MenuName,
+                x.IpAddress,
+                x.UserAgent,
+                x.CreatedAt
+            })
+            .ToListAsync(ct);
+
+        var rows = list.Select((x, idx) => new Response.Row
+        {
+            Num = total - skip - idx,
+            ID = x.ID,
+            UserID = x.UserID,
+            UserName = x.UserName,
+            Method = x.Method,
+            Path = x.Path,
+            QueryString = x.QueryString,
+            StatusCode = x.StatusCode,
+            ElapsedMs = x.ElapsedMs,
+            MenuName = x.MenuName,
+            IpAddress = x.IpAddress,
+            UserAgent = x.UserAgent,
+            CreatedAt = x.CreatedAt
+        }).ToList();
+
+        return new Response
+        {
+            Total = total,
+            List = rows
+        };
+    }
+}

+ 13 - 0
Application/Features/Director/AccessLog/Search/Query.cs

@@ -0,0 +1,13 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Director.AccessLog.Search;
+
+public sealed record Query(
+    int PageNum,
+    ushort PerPage,
+    int? Search,
+    string? Keyword,
+    string? Method,
+    string? StartAt,
+    string? EndAt
+) : IQuery<Response>;

+ 25 - 0
Application/Features/Director/AccessLog/Search/Response.cs

@@ -0,0 +1,25 @@
+namespace Application.Features.Director.AccessLog.Search;
+
+public sealed class Response
+{
+    public int Total { get; init; }
+
+    public required IReadOnlyList<Row> List { get; init; }
+
+    public sealed class Row
+    {
+        public int Num { get; init; }
+        public long ID { get; init; }
+        public required string UserID { get; init; }
+        public string? UserName { get; init; }
+        public required string Method { get; init; }
+        public required string Path { get; init; }
+        public string? QueryString { get; init; }
+        public int StatusCode { get; init; }
+        public long ElapsedMs { get; init; }
+        public string? MenuName { get; init; }
+        public string? IpAddress { get; init; }
+        public string? UserAgent { get; init; }
+        public required DateTime CreatedAt { get; init; }
+    }
+}

+ 5 - 0
Application/Features/Director/LoginLog/Delete/Command.cs

@@ -0,0 +1,5 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Director.LoginLog.Delete;
+
+public sealed record Command(int[] IDs) : ICommand;

+ 18 - 0
Application/Features/Director/LoginLog/Delete/Handler.cs

@@ -0,0 +1,18 @@
+using Application.Abstractions.Messaging;
+using Application.Abstractions.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Director.LoginLog.Delete;
+
+public sealed class Handler(IAppDbContext db) : ICommandHandler<Command>
+{
+    public async Task Handle(Command request, CancellationToken ct)
+    {
+        if (request.IDs is null || request.IDs.Length == 0)
+        {
+            return;
+        }
+
+        await db.AdminLoginLog.Where(x => request.IDs.Contains(x.ID)).ExecuteDeleteAsync(ct);
+    }
+}

+ 173 - 0
Application/Features/Director/LoginLog/Search/Handler.cs

@@ -0,0 +1,173 @@
+using Application.Abstractions.Messaging;
+using Application.Abstractions.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Director.LoginLog.Search;
+
+public sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
+{
+    public async Task<Response> Handle(Query request, CancellationToken ct)
+    {
+        var query = db.AdminLoginLog.AsNoTracking().AsQueryable();
+
+        // 키워드 검색
+        if (!string.IsNullOrWhiteSpace(request.Keyword))
+        {
+            var kw = request.Keyword.Trim();
+            query = request.Search switch
+            {
+                1 => query.Where(x => x.Account.Contains(kw)),
+                2 => query.Where(x => x.IpAddress != null && x.IpAddress.Contains(kw)),
+                _ => query.Where(x => x.Account.Contains(kw))
+            };
+        }
+
+        // 성공/실패 필터
+        if (request.Success.HasValue)
+        {
+            query = query.Where(x => x.Success == request.Success.Value);
+        }
+
+        // 날짜 필터
+        if (!string.IsNullOrWhiteSpace(request.StartAt) && DateTime.TryParse(request.StartAt, out var startAt))
+        {
+            query = query.Where(x => x.CreatedAt >= startAt);
+        }
+
+        if (!string.IsNullOrWhiteSpace(request.EndAt) && DateTime.TryParse(request.EndAt, out var endAt))
+        {
+            query = query.Where(x => x.CreatedAt <= endAt);
+        }
+
+        var total = await query.CountAsync(ct);
+        var skip = (request.PageNum - 1) * request.PerPage;
+
+        var list = await query
+            .OrderByDescending(x => x.ID)
+            .Skip(skip)
+            .Take(request.PerPage)
+            .Select(x => new
+            {
+                x.ID,
+                x.Account,
+                x.Success,
+                x.Reason,
+                x.IpAddress,
+                x.UserAgent,
+                x.CreatedAt
+            })
+            .ToListAsync(ct);
+
+        var rows = list.Select((x, idx) => new Response.Row
+        {
+            Num = total - skip - idx,
+            ID = x.ID,
+            Account = x.Account,
+            Success = x.Success,
+            Reason = x.Reason,
+            IpAddress = x.IpAddress,
+            UserAgent = x.UserAgent,
+            Browser = ParseUserAgent(x.UserAgent, "browser"),
+            OS = ParseUserAgent(x.UserAgent, "os"),
+            Device = ParseUserAgent(x.UserAgent, "device"),
+            CreatedAt = x.CreatedAt
+        }).ToList();
+
+        return new Response
+        {
+            Total = total,
+            List = rows
+        };
+    }
+
+    private static string? ParseUserAgent(string? userAgent, string type)
+    {
+        if (string.IsNullOrWhiteSpace(userAgent))
+        {
+            return null;
+        }
+
+        return type switch
+        {
+            "browser" => ExtractBrowser(userAgent),
+            "os" => ExtractOS(userAgent),
+            "device" => ExtractDevice(userAgent),
+            _ => null
+        };
+    }
+
+    private static string ExtractBrowser(string ua)
+    {
+        if (ua.Contains("Edg/"))
+        {
+            return "Edge";
+        }
+        if (ua.Contains("Chrome/"))
+        {
+            return "Chrome";
+        }
+        if (ua.Contains("Firefox/"))
+        {
+            return "Firefox";
+        }
+        if (ua.Contains("Safari/") && !ua.Contains("Chrome"))
+        {
+            return "Safari";
+        }
+        if (ua.Contains("MSIE") || ua.Contains("Trident/"))
+        {
+            return "IE";
+        }
+        return "Unknown";
+    }
+
+    private static string ExtractOS(string ua)
+    {
+        if (ua.Contains("Windows NT 10"))
+        {
+            return "Windows 10";
+        }
+        if (ua.Contains("Windows NT 6.3"))
+        {
+            return "Windows 8.1";
+        }
+        if (ua.Contains("Windows NT 6.1"))
+        {
+            return "Windows 7";
+        }
+        if (ua.Contains("Windows"))
+        {
+            return "Windows";
+        }
+        if (ua.Contains("Mac OS X"))
+        {
+            return "macOS";
+        }
+        if (ua.Contains("Android"))
+        {
+            return "Android";
+        }
+        if (ua.Contains("iPhone") || ua.Contains("iPad"))
+        {
+            return "iOS";
+        }
+        if (ua.Contains("Linux"))
+        {
+            return "Linux";
+        }
+        return "Unknown";
+    }
+
+    private static string ExtractDevice(string ua)
+    {
+        if (ua.Contains("Mobile") || (ua.Contains("Android") && !ua.Contains("Tablet")))
+        {
+            return "Mobile";
+        }
+        if (ua.Contains("Tablet") || ua.Contains("iPad"))
+        {
+            return "Tablet";
+        }
+        return "Desktop";
+    }
+}

+ 13 - 0
Application/Features/Director/LoginLog/Search/Query.cs

@@ -0,0 +1,13 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Director.LoginLog.Search;
+
+public sealed record Query(
+    int PageNum,
+    ushort PerPage,
+    int? Search,
+    string? Keyword,
+    bool? Success,
+    string? StartAt,
+    string? EndAt
+) : IQuery<Response>;

+ 23 - 0
Application/Features/Director/LoginLog/Search/Response.cs

@@ -0,0 +1,23 @@
+namespace Application.Features.Director.LoginLog.Search;
+
+public sealed class Response
+{
+    public int Total { get; init; }
+
+    public required IReadOnlyList<Row> List { get; init; }
+
+    public sealed class Row
+    {
+        public int Num { get; init; }
+        public int ID { get; init; }
+        public required string Account { get; init; }
+        public bool Success { get; init; }
+        public string? Reason { get; init; }
+        public string? IpAddress { get; init; }
+        public string? UserAgent { get; init; }
+        public string? Browser { get; init; }
+        public string? OS { get; init; }
+        public string? Device { get; init; }
+        public required DateTime CreatedAt { get; init; }
+    }
+}

+ 2 - 1
Application/Features/Member/List/Create/Handler.cs

@@ -2,6 +2,7 @@ using Application.Abstractions.Messaging;
 using Application.Abstractions.Data;
 using Domain.Entities.Members;
 using MemberEntity = Domain.Entities.Members.Member;
+using ChannelEntity = Domain.Entities.Members.Channel;
 using Microsoft.EntityFrameworkCore;
 
 namespace Application.Features.Member.List.Create;
@@ -59,7 +60,7 @@ public sealed class Handler(IAppDbContext db) : ICommandHandler<Command>
             !string.IsNullOrWhiteSpace(request.YouTubeName) &&
             !string.IsNullOrWhiteSpace(request.YouTubeUrl))
         {
-            await db.Channel.AddAsync(Channel.Create(
+            await db.Channel.AddAsync(ChannelEntity.Create(
                 member.ID,
                 request.YouTubeSID,
                 request.YouTubeName,

+ 64 - 0
Domain/Entities/Director/AdminAccessLog.cs

@@ -0,0 +1,64 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Domain.Entities.Director;
+
+/// <summary>
+/// 관리자 접근 기록
+/// </summary>
+public class AdminAccessLog
+{
+    [Key]
+    public long ID { get; private set; }
+
+    public string UserID { get; private set; } = default!;
+
+    public string? UserName { get; private set; }
+
+    public string Method { get; private set; } = default!;
+
+    public string Path { get; private set; } = default!;
+
+    public string? QueryString { get; private set; }
+
+    public int StatusCode { get; private set; }
+
+    public long ElapsedMs { get; private set; }
+
+    public string? MenuName { get; private set; }
+
+    public string? IpAddress { get; private set; }
+
+    public string? UserAgent { get; private set; }
+
+    public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
+
+    private AdminAccessLog() { }
+
+    public static AdminAccessLog Create(
+        string userId,
+        string? userName,
+        string method,
+        string path,
+        string? queryString,
+        int statusCode,
+        long elapsedMs,
+        string? menuName,
+        string? ipAddress,
+        string? userAgent
+    ) {
+        return new AdminAccessLog
+        {
+            UserID = userId.Length > 64 ? userId[..64] : userId,
+            UserName = userName?.Length > 100 ? userName[..100] : userName,
+            Method = method.Length > 10 ? method[..10] : method,
+            Path = path.Length > 2048 ? path[..2048] : path,
+            QueryString = queryString?.Length > 2048 ? queryString[..2048] : queryString,
+            StatusCode = statusCode,
+            ElapsedMs = elapsedMs,
+            MenuName = menuName?.Length > 200 ? menuName[..200] : menuName,
+            IpAddress = ipAddress?.Length > 45 ? ipAddress[..45] : ipAddress,
+            UserAgent = userAgent?.Length > 512 ? userAgent[..512] : userAgent,
+            CreatedAt = DateTime.UtcNow
+        };
+    }
+}

+ 67 - 0
Domain/Entities/Director/AdminLoginLog.cs

@@ -0,0 +1,67 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Domain.Entities.Director
+{
+    /// <summary>
+    /// 관리자 로그인 기록
+    /// </summary>
+    public class AdminLoginLog
+    {
+        [Key]
+        public int ID { get; private set; }
+
+        public bool Success { get; private set; } = false;
+
+        public string Account { get; private set; } = default!;
+
+        public string? Reason { get; private set; }
+
+        public string? IpAddress { get; private set; }
+
+        public string? UserAgent { get; private set; }
+
+        public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
+
+        private AdminLoginLog() { }
+
+        private AdminLoginLog(bool success, string account, string? reason, string? ipAddress, string? userAgent)
+        {
+            if (string.IsNullOrWhiteSpace(account))
+            {
+                throw new ArgumentException("Account is required.", nameof(account));
+            }
+
+            if (account.Length > 120)
+            {
+                throw new ArgumentOutOfRangeException(nameof(account));
+            }
+
+            if (reason is not null && reason.Length > 225)
+            {
+                throw new ArgumentOutOfRangeException(nameof(reason));
+            }
+
+            if (ipAddress is not null && ipAddress.Length > 45)
+            {
+                throw new ArgumentOutOfRangeException(nameof(ipAddress));
+            }
+
+            if (userAgent is not null && userAgent.Length > 512)
+            {
+                throw new ArgumentOutOfRangeException(nameof(userAgent));
+            }
+
+            Success = success;
+            Account = account;
+            Reason = reason;
+            IpAddress = ipAddress;
+            UserAgent = userAgent;
+            CreatedAt = DateTime.UtcNow;
+        }
+
+        public static AdminLoginLog Create(bool success, string account, string? reason = null, string? ipAddress = null, string? userAgent = null)
+        {
+            return new(success, account, reason, ipAddress, userAgent);
+        }
+    }
+}

+ 1 - 1
Domain/Entities/EmailVerification/ValueObject/AdditionalData.cs

@@ -3,6 +3,6 @@ namespace Domain.Entities.EmailVerification.ValueObject
     // 추가 저장 값들
     public record AdditionalData
     {
-        public string? Email { get; init; } // 이메일 변경 시 
+        public string? Email { get; init; } // 이메일 변경 시
     }
 }

+ 1 - 1
Domain/Entities/Members/Channel.cs

@@ -122,4 +122,4 @@ namespace Domain.Entities.Members
             UpdatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Members/Logs/MemberEmailChangeLog.cs

@@ -52,4 +52,4 @@ namespace Domain.Entities.Members.Logs
             CreatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 6 - 6
Domain/Entities/Members/Logs/MemberIntroChangeLog.cs

@@ -16,15 +16,15 @@ namespace Domain.Entities.Members.Logs
 
         public int MemberID { get; private set; }
 
-        public string? BeforeIntro { get; private set; } = null;
+        public string? BeforeIntro { get; private set; } = default;
 
-        public string? AfterIntro { get; private set; } = null;
+        public string? AfterIntro { get; private set; } = default;
 
-        public string? Referer { get; private set; } = null;
+        public string? Referer { get; private set; } = default;
 
-        public string? IpAddress { get; private set; } = null;
+        public string? IpAddress { get; private set; } = default;
 
-        public string? UserAgent { get; private set; } = null;
+        public string? UserAgent { get; private set; } = default;
 
         public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
 
@@ -71,4 +71,4 @@ namespace Domain.Entities.Members.Logs
             return new(memberID, beforeIntro, afterIntro, referer, ipAddress, userAgent);
         }
     }
-}
+}

+ 2 - 2
Domain/Entities/Members/Logs/MemberLoginLog.cs

@@ -18,7 +18,7 @@ namespace Domain.Entities.Members.Logs
 
         public bool Success { get; private set; } = false;
 
-        public string Account { get; private set; } = null!;
+        public string Account { get; private set; } = default!;
 
         public string? Reason { get; private set; }
 
@@ -82,4 +82,4 @@ namespace Domain.Entities.Members.Logs
             return new(memberID, success, account, reason, referer, url, ipAddress, userAgent);
         }
     }
-}
+}

+ 6 - 6
Domain/Entities/Members/Logs/MemberNameChangeLog.cs

@@ -16,15 +16,15 @@ namespace Domain.Entities.Members.Logs
 
         public int MemberID { get; private set; }
 
-        public string? BeforeName { get; private set; } = null;
+        public string? BeforeName { get; private set; } = default;
 
-        public string? AfterName { get; private set; } = null;
+        public string? AfterName { get; private set; } = default;
 
-        public string? Referer { get; private set; } = null;
+        public string? Referer { get; private set; } = default;
 
-        public string? IpAddress { get; private set; } = null;
+        public string? IpAddress { get; private set; } = default;
 
-        public string? UserAgent { get; private set; } = null;
+        public string? UserAgent { get; private set; } = default;
 
         public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
 
@@ -71,4 +71,4 @@ namespace Domain.Entities.Members.Logs
             return new(memberID, beforeName, afterName, referer, ipAddress, userAgent);
         }
     }
-}
+}

+ 6 - 6
Domain/Entities/Members/Logs/MemberSummaryChangeLog.cs

@@ -16,15 +16,15 @@ namespace Domain.Entities.Members.Logs
 
         public int MemberID { get; private set; }
 
-        public string? BeforeSummary { get; private set; } = null;
+        public string? BeforeSummary { get; private set; } = default;
 
-        public string? AfterSummary { get; private set; } = null;
+        public string? AfterSummary { get; private set; } = default;
 
-        public string? Referer { get; private set; } = null;
+        public string? Referer { get; private set; } = default;
 
-        public string? IpAddress { get; private set; } = null;
+        public string? IpAddress { get; private set; } = default;
 
-        public string? UserAgent { get; private set; } = null;
+        public string? UserAgent { get; private set; } = default;
 
         public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
 
@@ -71,4 +71,4 @@ namespace Domain.Entities.Members.Logs
             return new(memberID, beforeSummary, afterSummary, referer, ipAddress, userAgent);
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Page/Banner/Item.cs

@@ -137,4 +137,4 @@ namespace Domain.Entities.Page.Banner
             UpdatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Page/Banner/Position.cs

@@ -70,4 +70,4 @@ namespace Domain.Entities.Page.Banner
             UpdatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Page/Document.cs

@@ -86,4 +86,4 @@ namespace Domain.Entities.Page
             Views++;
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Page/Faq/Category.cs

@@ -90,4 +90,4 @@ namespace Domain.Entities.Page.Faq
             UpdatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Page/Faq/Item.cs

@@ -97,4 +97,4 @@ namespace Domain.Entities.Page.Faq
             UpdatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 1 - 1
Domain/Entities/Page/Popup.cs

@@ -91,4 +91,4 @@ namespace Domain.Entities.Page
             UpdatedAt = DateTime.UtcNow;
         }
     }
-}
+}

+ 9 - 4
Infrastructure/Persistence/AppDbContext.cs

@@ -9,6 +9,7 @@ using Domain.Entities.Wallets;
 using Domain.Entities.Forum.Boards;
 using Domain.Entities.Forum.Posts;
 using Domain.Entities.Forum.Comments;
+using Domain.Entities.Director;
 using Domain.Entities.Forum.Logs;
 using Microsoft.EntityFrameworkCore;
 
@@ -18,6 +19,10 @@ namespace Infrastructure.Persistence
     {
         public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
 
+        // Admin
+        public DbSet<AdminLoginLog> AdminLoginLog { get; set; }
+        public DbSet<AdminAccessLog> AdminAccessLog { get; set; }
+
         // Config
         public DbSet<Config> Config { get; set; }
 
@@ -46,14 +51,14 @@ namespace Infrastructure.Persistence
         // Channel
         public DbSet<Channel> Channel { get; set; }
 
-        // 게시판
+        // 占쌉쏙옙占쏙옙
         public DbSet<BoardGroup> BoardGroup { get; set; }
         public DbSet<Board> Board { get; set; }
         public DbSet<BoardMeta> BoardMeta { get; set; }
         public DbSet<BoardManager> BoardManager { get; set; }
         public DbSet<BoardPrefix> BoardPrefix { get; set; }
 
-        // 게시글
+        // 占쌉시깍옙
         public DbSet<Post> Post { get; set; }
         public DbSet<PostImage> PostImage { get; set; }
         public DbSet<PostMedia> PostMedia { get; set; }
@@ -65,7 +70,7 @@ namespace Infrastructure.Persistence
         public DbSet<PostTag> PostTag { get; set; }
         public DbSet<Tag> Tag { get; set; }
 
-        // 댓글
+        // 占쏙옙占�
         public DbSet<Comment> Comment { get; set; }
         public DbSet<CommentFile> CommentFile { get; set; }
         public DbSet<CommentImage> CommentImage { get; set; }
@@ -75,7 +80,7 @@ namespace Infrastructure.Persistence
         public DbSet<CommentReport> CommentReport { get; set; }
         public DbSet<CommentMention> CommentMention { get; set; }
 
-        // 게시글/댓글 로그
+        // 占쌉시깍옙/占쏙옙占� 占싸깍옙
         public DbSet<PostUpdateLog> PostUpdateLog { get; set; }
         public DbSet<PostFileDownLog> PostFileDownLog { get; set; }
         public DbSet<PostLinkClickLog> PostLinkClickLog { get; set; }

+ 30 - 0
Infrastructure/Persistence/Configurations/Director/AdminAccessLogConfiguration.cs

@@ -0,0 +1,30 @@
+using Domain.Entities.Director;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Infrastructure.Persistence.Configurations.Director
+{
+    public sealed class AdminAccessLogConfiguration : IEntityTypeConfiguration<AdminAccessLog>
+    {
+        public void Configure(EntityTypeBuilder<AdminAccessLog> builder)
+        {
+            builder.HasIndex(x => x.UserID);
+            builder.HasIndex(x => x.CreatedAt);
+
+            builder.ToTable(nameof(AdminAccessLog), x => x.HasComment("관리자 접근 기록"));
+            builder.HasKey(x => x.ID);
+            builder.Property(x => x.ID).ValueGeneratedOnAdd().HasComment("PK");
+            builder.Property(x => x.UserID).HasMaxLength(64).IsRequired().HasComment("관리자 사용자 ID");
+            builder.Property(x => x.UserName).HasMaxLength(100).HasComment("관리자 사용자 이름");
+            builder.Property(x => x.Method).HasMaxLength(10).IsRequired().HasComment("HTTP Method");
+            builder.Property(x => x.Path).HasMaxLength(2048).IsRequired().HasComment("요청 경로");
+            builder.Property(x => x.QueryString).HasMaxLength(2048).HasComment("쿼리 스트링");
+            builder.Property(x => x.StatusCode).IsRequired().HasComment("응답 상태 코드");
+            builder.Property(x => x.ElapsedMs).IsRequired().HasComment("처리 시간 (밀리초)");
+            builder.Property(x => x.MenuName).HasMaxLength(200).HasComment("메뉴 이름");
+            builder.Property(x => x.IpAddress).HasMaxLength(45).HasComment("IP 주소");
+            builder.Property(x => x.UserAgent).HasMaxLength(512).HasComment("User Agent");
+            builder.Property(x => x.CreatedAt).IsRequired().HasComment("생성 일시");
+        }
+    }
+}

+ 24 - 0
Infrastructure/Persistence/Configurations/Director/AdminLoginLogConfiguration.cs

@@ -0,0 +1,24 @@
+using Domain.Entities.Director;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Infrastructure.Persistence.Configurations.Director;
+
+public sealed class AdminLoginLogConfiguration : IEntityTypeConfiguration<AdminLoginLog>
+{
+    public void Configure(EntityTypeBuilder<AdminLoginLog> builder)
+    {
+        builder.HasIndex(x => x.Account);
+        builder.HasIndex(x => x.CreatedAt);
+
+        builder.ToTable(nameof(AdminLoginLog), x => x.HasComment("관리자 로그인 기록"));
+        builder.HasKey(x => x.ID);
+        builder.Property(x => x.ID).ValueGeneratedOnAdd().HasComment("PK");
+        builder.Property(x => x.Success).IsRequired().HasComment("로그인 성공 여부");
+        builder.Property(x => x.Account).HasMaxLength(120).IsRequired().HasComment("로그인 시도 계정");
+        builder.Property(x => x.Reason).HasMaxLength(225).HasComment("실패 사유");
+        builder.Property(x => x.IpAddress).HasMaxLength(45).HasComment("IP 주소");
+        builder.Property(x => x.UserAgent).HasMaxLength(512).HasComment("User Agent");
+        builder.Property(x => x.CreatedAt).IsRequired().HasComment("생성 일시");
+    }
+}

+ 5718 - 0
Infrastructure/Persistence/Migrations/20260212015759_a6.Designer.cs

@@ -0,0 +1,5718 @@
+// <auto-generated />
+using System;
+using Infrastructure.Persistence;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Infrastructure.Persistence.Migrations
+{
+    [DbContext(typeof(AppDbContext))]
+    [Migration("20260212015759_a6")]
+    partial class a6
+    {
+        /// <inheritdoc />
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "10.0.2")
+                .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+            modelBuilder.Entity("Domain.Entities.Common.Config", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("LastUpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 수정일시");
+
+                    b.Property<byte[]>("RowVersion")
+                        .IsConcurrencyToken()
+                        .IsRequired()
+                        .ValueGeneratedOnAddOrUpdate()
+                        .HasColumnType("rowversion")
+                        .HasComment("동시성 제어용");
+
+                    b.HasKey("ID");
+
+                    b.ToTable("Config", null, t =>
+                        {
+                            t.HasComment("운영 정보 설정 값");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Director.AdminAccessLog", b =>
+                {
+                    b.Property<long>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("생성 일시");
+
+                    b.Property<long>("ElapsedMs")
+                        .HasColumnType("bigint")
+                        .HasComment("처리 시간 (밀리초)");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP 주소");
+
+                    b.Property<string>("MenuName")
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("메뉴 이름");
+
+                    b.Property<string>("Method")
+                        .IsRequired()
+                        .HasMaxLength(10)
+                        .HasColumnType("nvarchar(10)")
+                        .HasComment("HTTP Method");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasMaxLength(2048)
+                        .HasColumnType("nvarchar(2048)")
+                        .HasComment("요청 경로");
+
+                    b.Property<string>("QueryString")
+                        .HasMaxLength(2048)
+                        .HasColumnType("nvarchar(2048)")
+                        .HasComment("쿼리 스트링");
+
+                    b.Property<int>("StatusCode")
+                        .HasColumnType("int")
+                        .HasComment("응답 상태 코드");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.Property<string>("UserID")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("nvarchar(64)")
+                        .HasComment("관리자 사용자 ID");
+
+                    b.Property<string>("UserName")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("관리자 사용자 이름");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("UserID");
+
+                    b.ToTable("AdminAccessLog", null, t =>
+                        {
+                            t.HasComment("관리자 접근 기록");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Director.AdminLoginLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Account")
+                        .IsRequired()
+                        .HasMaxLength(120)
+                        .HasColumnType("nvarchar(120)")
+                        .HasComment("로그인 시도 계정");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("생성 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP 주소");
+
+                    b.Property<string>("Reason")
+                        .HasMaxLength(225)
+                        .HasColumnType("nvarchar(225)")
+                        .HasComment("실패 사유");
+
+                    b.Property<bool>("Success")
+                        .HasColumnType("bit")
+                        .HasComment("로그인 성공 여부");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Account");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.ToTable("AdminLoginLog", null, t =>
+                        {
+                            t.HasComment("관리자 로그인 기록");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.EmailVerification.EmailVerifyNumber", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Code")
+                        .IsRequired()
+                        .HasMaxLength(10)
+                        .HasColumnType("nvarchar(10)")
+                        .HasComment("Code");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Email")
+                        .IsRequired()
+                        .HasMaxLength(60)
+                        .HasColumnType("nvarchar(60)")
+                        .HasComment("이메일");
+
+                    b.Property<DateTime>("Expiration")
+                        .HasColumnType("datetime2")
+                        .HasComment("만료 일시");
+
+                    b.Property<bool>("IsVerified")
+                        .HasColumnType("bit")
+                        .HasComment("인증 여부");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("int")
+                        .HasComment("인증 유형 (이메일 인증 / 비밀번호 재설정)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code");
+
+                    b.HasIndex("Email");
+
+                    b.HasIndex("Expiration");
+
+                    b.HasIndex("IsVerified");
+
+                    b.HasIndex("Type");
+
+                    b.HasIndex("Type", "Code");
+
+                    b.HasIndex("Type", "Code", "IsVerified");
+
+                    b.ToTable("EmailVerifyNumber", null, t =>
+                        {
+                            t.HasComment("이메일 인증 번호들");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.EmailVerification.EmailVerifyToken", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Additional")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("추가 정보(JSON)");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Email")
+                        .IsRequired()
+                        .HasMaxLength(60)
+                        .HasColumnType("nvarchar(60)")
+                        .HasComment("이메일");
+
+                    b.Property<DateTime>("Expiration")
+                        .HasColumnType("datetime2")
+                        .HasComment("만료 일시");
+
+                    b.Property<bool>("IsVerified")
+                        .HasColumnType("bit")
+                        .HasComment("인증 여부");
+
+                    b.Property<string>("Token")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("nvarchar(256)")
+                        .HasComment("Token");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("int")
+                        .HasComment("인증 유형 (이메일 인증 / 비밀번호 재설정)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Email");
+
+                    b.HasIndex("Expiration");
+
+                    b.HasIndex("IsVerified");
+
+                    b.HasIndex("Token");
+
+                    b.HasIndex("Type");
+
+                    b.HasIndex("Type", "Email", "Token");
+
+                    b.HasIndex("Type", "Email", "Token", "IsVerified");
+
+                    b.ToTable("EmailVerifyToken", null, t =>
+                        {
+                            t.HasComment("이메일 인증 토큰들");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.Board", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardGroupID")
+                        .HasColumnType("int")
+                        .HasComment("분류 ID");
+
+                    b.Property<string>("Code")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("게시판 주소");
+
+                    b.Property<int>("Comments")
+                        .HasColumnType("int")
+                        .HasComment("댓글 수");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<bool>("IsSearch")
+                        .HasColumnType("bit")
+                        .HasComment("검색 여부");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(70)
+                        .HasColumnType("nvarchar(70)")
+                        .HasComment("게시판 이름");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<int>("Posts")
+                        .HasColumnType("int")
+                        .HasComment("게시글 수");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardGroupID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("Comments");
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("IsSearch");
+
+                    b.HasIndex("Name");
+
+                    b.HasIndex("Order");
+
+                    b.HasIndex("Posts");
+
+                    b.HasIndex("Code", "IsActive");
+
+                    b.HasIndex("Code", "IsSearch");
+
+                    b.HasIndex("IsSearch", "IsActive");
+
+                    b.ToTable("Board", null, t =>
+                        {
+                            t.HasComment("게시판");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardGroup", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<short>("Boards")
+                        .HasColumnType("smallint")
+                        .HasComment("게시판 수");
+
+                    b.Property<string>("Code")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("게시판 분류 주소");
+
+                    b.Property<int>("Comments")
+                        .HasColumnType("int")
+                        .HasComment("댓글 수");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(70)
+                        .HasColumnType("nvarchar(70)")
+                        .HasComment("게시판 분류 명");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<int>("Posts")
+                        .HasColumnType("int")
+                        .HasComment("게시글 수");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("Order");
+
+                    b.HasIndex("Order", "CreatedAt");
+
+                    b.HasIndex("Code", "Order", "CreatedAt");
+
+                    b.ToTable("BoardGroup", null, t =>
+                        {
+                            t.HasComment("게시판 분류");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardManager", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<bool>("CanDelete")
+                        .HasColumnType("bit")
+                        .HasComment("삭제 권한");
+
+                    b.Property<bool>("CanEdit")
+                        .HasColumnType("bit")
+                        .HasComment("수정 권한");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("관리자 ID");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("BoardID", "MemberID");
+
+                    b.ToTable("BoardManager", null, t =>
+                        {
+                            t.HasComment("게시판 관리자");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardMeta", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID")
+                        .IsUnique();
+
+                    b.ToTable("BoardMeta", null, t =>
+                        {
+                            t.HasComment("게시판 설정");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardPrefix", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<string>("Color")
+                        .HasMaxLength(10)
+                        .HasColumnType("nvarchar(10)")
+                        .HasComment("색상");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(20)
+                        .HasColumnType("nvarchar(20)")
+                        .HasComment("말머리");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("정렬 순서");
+
+                    b.Property<int>("Posts")
+                        .HasColumnType("int")
+                        .HasComment("사용 게시글 수");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID", "IsActive", "Order", "CreatedAt");
+
+                    b.ToTable("BoardPrefix", null, t =>
+                        {
+                            t.HasComment("게시판 말머리");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.Comment", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<string>("Content")
+                        .IsRequired()
+                        .HasMaxLength(4000)
+                        .HasColumnType("nvarchar(4000)")
+                        .HasComment("댓글 내용");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<DateTime?>("DeletedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<short>("Depth")
+                        .HasColumnType("smallint")
+                        .HasComment("댓글 깊이");
+
+                    b.Property<int>("Dislikes")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Email")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<byte>("Files")
+                        .HasColumnType("tinyint");
+
+                    b.Property<int>("Images")
+                        .HasColumnType("int");
+
+                    b.Property<string>("IpAddress")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<bool>("IsDeleted")
+                        .HasColumnType("bit")
+                        .HasComment("삭제 여부");
+
+                    b.Property<bool>("IsReply")
+                        .HasColumnType("bit");
+
+                    b.Property<bool>("IsSecret")
+                        .HasColumnType("bit");
+
+                    b.Property<int>("Likes")
+                        .HasColumnType("int");
+
+                    b.Property<byte>("Medias")
+                        .HasColumnType("tinyint");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<int?>("MentionMemberID")
+                        .HasColumnType("int")
+                        .HasComment("언급 대상 회원 ID");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int?>("ParentID")
+                        .HasColumnType("int")
+                        .HasComment("부모 댓글 ID");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<int>("Replies")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Reports")
+                        .HasColumnType("int");
+
+                    b.Property<string>("SID")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("Score")
+                        .HasColumnType("int")
+                        .HasComment("점수");
+
+                    b.Property<byte>("Status")
+                        .HasColumnType("tinyint")
+                        .HasComment("댓글 상태");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("UserAgent")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("MentionMemberID");
+
+                    b.HasIndex("ParentID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("PostID", "IsDeleted", "ParentID", "CreatedAt")
+                        .IsDescending(false, false, false, true);
+
+                    b.HasIndex("PostID", "IsDeleted", "ParentID", "Score", "ID")
+                        .IsDescending(false, false, false, true, true);
+
+                    b.HasIndex("PostID", "MemberID", "IsDeleted", "ParentID", "CreatedAt")
+                        .IsDescending(false, false, false, false, true);
+
+                    b.HasIndex("PostID", "MemberID", "IsDeleted", "ParentID", "Score", "ID")
+                        .IsDescending(false, false, false, false, true, true);
+
+                    b.ToTable("Comment", null, t =>
+                        {
+                            t.HasComment("댓글");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentFile", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int");
+
+                    b.Property<string>("ContentType")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<int>("Downloads")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Extension")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<string>("FileName")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<string>("HashedName")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<long?>("Size")
+                        .HasColumnType("bigint");
+
+                    b.Property<Guid>("UUID")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("UUID")
+                        .IsUnique();
+
+                    b.ToTable("CommentFile", null, t =>
+                        {
+                            t.HasComment("댓글 파일");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentImage", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int");
+
+                    b.Property<string>("ContentType")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("Extension")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<string>("FileName")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<string>("HashedName")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<short?>("Height")
+                        .HasColumnType("smallint");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<long?>("Size")
+                        .HasColumnType("bigint");
+
+                    b.Property<Guid>("UUID")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<short?>("Width")
+                        .HasColumnType("smallint");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("UUID")
+                        .IsUnique();
+
+                    b.ToTable("CommentImage", null, t =>
+                        {
+                            t.HasComment("댓글 이미지");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentLink", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Clicks")
+                        .HasColumnType("int");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<Guid>("UUID")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("PostID");
+
+                    b.ToTable("CommentLink", null, t =>
+                        {
+                            t.HasComment("댓글 링크");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentMedia", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("PostID");
+
+                    b.ToTable("CommentMedia", null, t =>
+                        {
+                            t.HasComment("댓글 미디어");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentMention", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<int>("Length")
+                        .HasColumnType("int")
+                        .HasComment("길이");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("언급된 회원 ID");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<string>("RawHandle")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("nvarchar(64)")
+                        .HasComment("원문 회원 언급값");
+
+                    b.Property<int>("Start")
+                        .HasColumnType("int")
+                        .HasComment("본문 내 시작 인덱스");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID")
+                        .IsUnique();
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("CommentID", "ID");
+
+                    b.HasIndex("CommentID", "MemberID", "ID");
+
+                    b.HasIndex("CommentID", "MemberID", "Start");
+
+                    b.ToTable("CommentMention", null, t =>
+                        {
+                            t.HasComment("댓글 멘션");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentReaction", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<byte>("Reaction")
+                        .HasColumnType("tinyint")
+                        .HasComment("반응 구분");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("Reaction");
+
+                    b.HasIndex("CommentID", "ID");
+
+                    b.HasIndex("CommentID", "MemberID")
+                        .IsUnique();
+
+                    b.ToTable("CommentReaction", null, t =>
+                        {
+                            t.HasComment("댓글 반응");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentReport", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Memo")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("처리 내용");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<string>("Reason")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("신고 내용");
+
+                    b.Property<byte>("Status")
+                        .HasColumnType("tinyint")
+                        .HasComment("처리 상태");
+
+                    b.Property<byte>("Type")
+                        .HasColumnType("tinyint")
+                        .HasComment("신고 사유");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("Status");
+
+                    b.HasIndex("Type");
+
+                    b.HasIndex("CommentID", "ID");
+
+                    b.HasIndex("CommentID", "MemberID")
+                        .IsUnique();
+
+                    b.ToTable("CommentReport", null, t =>
+                        {
+                            t.HasComment("댓글 신고");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.CommentFileDownLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CommentFileID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 파일 ID");
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int?>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CommentFileID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("CommentID", "CommentFileID");
+
+                    b.HasIndex("CommentID", "CommentFileID", "MemberID");
+
+                    b.ToTable("CommentFileDownLog", null, t =>
+                        {
+                            t.HasComment("댓글 파일 다운로드 로그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.CommentLinkClickLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 ID");
+
+                    b.Property<int>("CommentLinkID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 링크 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int?>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("CommentLinkID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("CommentID", "CommentLinkID");
+
+                    b.HasIndex("CommentID", "CommentLinkID", "MemberID");
+
+                    b.ToTable("CommentLinkClickLog", null, t =>
+                        {
+                            t.HasComment("댓글 링크 클릭 로그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.CommentUpdateLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CommentID")
+                        .HasColumnType("int")
+                        .HasComment("댓글 ID");
+
+                    b.Property<string>("ContentDiff")
+                        .HasMaxLength(4000)
+                        .HasColumnType("nvarchar(4000)")
+                        .HasComment("변경 내용");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Note")
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("비고");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CommentID");
+
+                    b.HasIndex("ContentDiff");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("CommentID", "ID");
+
+                    b.HasIndex("CommentID", "MemberID");
+
+                    b.HasIndex("CommentID", "MemberID", "ID");
+
+                    b.ToTable("CommentUpdateLog", null, t =>
+                        {
+                            t.HasComment("댓글 수정 로그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.PostFileDownLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int?>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<int>("PostFileID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 파일 ID");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostFileID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("PostID", "PostFileID");
+
+                    b.HasIndex("PostID", "PostFileID", "MemberID");
+
+                    b.ToTable("PostFileDownLog", null, t =>
+                        {
+                            t.HasComment("게시글 파일 다운로드 로그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.PostLinkClickLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int?>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<int>("PostLinkID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 파일 ID");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("PostLinkID");
+
+                    b.HasIndex("PostID", "PostLinkID");
+
+                    b.HasIndex("PostID", "PostLinkID", "MemberID");
+
+                    b.ToTable("PostLinkClickLog", null, t =>
+                        {
+                            t.HasComment("게시글 링크 클릭 로그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.PostUpdateLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("ContentDiff")
+                        .HasMaxLength(4000)
+                        .HasColumnType("nvarchar(4000)")
+                        .HasComment("변경 내용");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Note")
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("비고");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<string>("SubjectDiff")
+                        .HasMaxLength(4000)
+                        .HasColumnType("nvarchar(4000)")
+                        .HasComment("변경 제목");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("ContentDiff");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("SubjectDiff");
+
+                    b.HasIndex("PostID", "ID");
+
+                    b.HasIndex("PostID", "MemberID");
+
+                    b.HasIndex("PostID", "MemberID", "ID");
+
+                    b.ToTable("PostUpdateLog", null, t =>
+                        {
+                            t.HasComment("게시글 수정 로그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.Post", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<int?>("BoardPrefixID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 말머리 ID");
+
+                    b.Property<int>("Bookmarks")
+                        .HasColumnType("int")
+                        .HasComment("즐겨찾기 수");
+
+                    b.Property<int>("Comments")
+                        .HasColumnType("int")
+                        .HasComment("댓글 수");
+
+                    b.Property<string>("Content")
+                        .IsRequired()
+                        .HasMaxLength(8000)
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("내용");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<DateTime?>("DeletedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("삭제 일시");
+
+                    b.Property<int>("Dislikes")
+                        .HasColumnType("int")
+                        .HasComment("싫어요");
+
+                    b.Property<string>("Email")
+                        .HasMaxLength(60)
+                        .HasColumnType("nvarchar(60)")
+                        .HasComment("회원 이메일");
+
+                    b.Property<byte>("Files")
+                        .HasColumnType("tinyint")
+                        .HasComment("파일 수");
+
+                    b.Property<byte>("Images")
+                        .HasColumnType("tinyint")
+                        .HasComment("이미지 수");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP");
+
+                    b.Property<bool>("IsAnonymous")
+                        .HasColumnType("bit")
+                        .HasComment("익명 글 여부");
+
+                    b.Property<bool>("IsDeleted")
+                        .HasColumnType("bit")
+                        .HasComment("삭제 여부");
+
+                    b.Property<bool>("IsNotice")
+                        .HasColumnType("bit")
+                        .HasComment("일반 공지 여부");
+
+                    b.Property<bool>("IsReply")
+                        .HasColumnType("bit")
+                        .HasComment("답변 여부");
+
+                    b.Property<bool>("IsSecret")
+                        .HasColumnType("bit")
+                        .HasComment("비밀글 여부");
+
+                    b.Property<bool>("IsSpeaker")
+                        .HasColumnType("bit")
+                        .HasComment("전체 공지 여부");
+
+                    b.Property<DateTime?>("LastCommentUpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 댓글 일시");
+
+                    b.Property<DateTime?>("LastReplyUpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 답변 일시");
+
+                    b.Property<int>("Likes")
+                        .HasColumnType("int")
+                        .HasComment("좋아요");
+
+                    b.Property<byte>("Medias")
+                        .HasColumnType("tinyint")
+                        .HasComment("미디어 수");
+
+                    b.Property<int?>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Name")
+                        .HasMaxLength(20)
+                        .HasColumnType("nvarchar(20)")
+                        .HasComment("회원 이름");
+
+                    b.Property<int>("Reports")
+                        .HasColumnType("int")
+                        .HasComment("신고 수");
+
+                    b.Property<string>("SID")
+                        .HasMaxLength(20)
+                        .HasColumnType("nvarchar(20)")
+                        .HasComment("회원 SID");
+
+                    b.Property<string>("Subject")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("제목");
+
+                    b.Property<byte>("Tags")
+                        .HasColumnType("tinyint")
+                        .HasComment("Tag 수");
+
+                    b.Property<string>("Thumbnail")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("대표 이미지");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-Agent");
+
+                    b.Property<int>("Views")
+                        .HasColumnType("int")
+                        .HasComment("조회 수");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("BoardPrefixID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("ID", "BoardID");
+
+                    b.HasIndex("ID", "BoardID", "IsDeleted");
+
+                    b.HasIndex("ID", "BoardID", "BoardPrefixID", "IsDeleted", "Comments");
+
+                    b.HasIndex("ID", "BoardID", "BoardPrefixID", "IsDeleted", "CreatedAt");
+
+                    b.HasIndex("ID", "BoardID", "BoardPrefixID", "IsDeleted", "Likes");
+
+                    b.HasIndex("ID", "BoardID", "BoardPrefixID", "IsDeleted", "Views");
+
+                    b.ToTable("Post", null, t =>
+                        {
+                            t.HasComment("게시글");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostBookmark", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("IpAddress")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<string>("UserAgent")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("PostID", "MemberID")
+                        .IsUnique();
+
+                    b.ToTable("PostBookmark", null, t =>
+                        {
+                            t.HasComment("게시글 즐겨찾기");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostFile", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<string>("ContentType")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<int>("Downloads")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Extension")
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<string>("FileName")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<string>("HashedName")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<long?>("Size")
+                        .HasColumnType("bigint");
+
+                    b.Property<Guid>("UUID")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("UUID")
+                        .IsUnique();
+
+                    b.ToTable("PostFile", null, t =>
+                        {
+                            t.HasComment("게시글 파일");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostImage", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<string>("ContentType")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("MIME 타입");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("비활성 일시");
+
+                    b.Property<string>("Extension")
+                        .HasMaxLength(10)
+                        .HasColumnType("nvarchar(10)")
+                        .HasComment("확장자");
+
+                    b.Property<string>("FileName")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("원본 파일명");
+
+                    b.Property<string>("HashedName")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("저장 파일명");
+
+                    b.Property<short?>("Height")
+                        .HasColumnType("smallint")
+                        .HasComment("세로 해상도(px)");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit")
+                        .HasComment("비활성 여부");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("저장 경로");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<long?>("Size")
+                        .HasColumnType("bigint")
+                        .HasComment("용량(byte)");
+
+                    b.Property<Guid>("UUID")
+                        .HasColumnType("uniqueidentifier")
+                        .HasComment("이미지 ID");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("URL");
+
+                    b.Property<short?>("Width")
+                        .HasColumnType("smallint")
+                        .HasComment("가로 해상도(px)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("UUID")
+                        .IsUnique();
+
+                    b.HasIndex("PostID", "HashedName");
+
+                    b.HasIndex("PostID", "HashedName", "IsDisabled");
+
+                    b.ToTable("PostImage", null, t =>
+                        {
+                            t.HasComment("게시글 이미지");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostLink", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Clicks")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<Guid>("UUID")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("PostID");
+
+                    b.ToTable("PostLink", null, t =>
+                        {
+                            t.HasComment("게시글 링크");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostMedia", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime?>("DisabledAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<bool>("IsDisabled")
+                        .HasColumnType("bit");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Url")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("PostID");
+
+                    b.ToTable("PostMedia", null, t =>
+                        {
+                            t.HasComment("게시글 미디어");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostReaction", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<byte>("Reaction")
+                        .HasColumnType("tinyint")
+                        .HasComment("반응 구분");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("Reaction");
+
+                    b.HasIndex("PostID", "MemberID")
+                        .IsUnique();
+
+                    b.ToTable("PostReaction", null, t =>
+                        {
+                            t.HasComment("게시글 반응");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostReport", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int")
+                        .HasComment("게시판 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Memo")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("처리 내용");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int")
+                        .HasComment("게시글 ID");
+
+                    b.Property<string>("Reason")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("신고 내용");
+
+                    b.Property<byte>("Status")
+                        .HasColumnType("tinyint")
+                        .HasComment("처리 상태");
+
+                    b.Property<byte>("Type")
+                        .HasColumnType("tinyint")
+                        .HasComment("신고 사유");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("Status");
+
+                    b.HasIndex("Type");
+
+                    b.HasIndex("PostID", "MemberID")
+                        .IsUnique();
+
+                    b.ToTable("PostReport", null, t =>
+                        {
+                            t.HasComment("게시글 신고");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostTag", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BoardID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<int>("PostID")
+                        .HasColumnType("int");
+
+                    b.Property<int>("TagID")
+                        .HasColumnType("int");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BoardID");
+
+                    b.HasIndex("PostID");
+
+                    b.HasIndex("TagID");
+
+                    b.ToTable("PostTag", null, t =>
+                        {
+                            t.HasComment("게시글 태그 연결");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.Tag", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(450)");
+
+                    b.Property<string>("Slug")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(450)");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<long>("UsageCount")
+                        .HasColumnType("bigint");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Name")
+                        .IsUnique();
+
+                    b.HasIndex("Slug")
+                        .IsUnique();
+
+                    b.HasIndex("UsageCount");
+
+                    b.ToTable("Tag", null, t =>
+                        {
+                            t.HasComment("태그");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Channel", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Handle")
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("핸들");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("활성 여부");
+
+                    b.Property<bool>("IsVerified")
+                        .HasColumnType("bit")
+                        .HasComment("인증 여부");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("채널 이름");
+
+                    b.Property<decimal>("PlatformFeeRate")
+                        .HasPrecision(5, 2)
+                        .HasColumnType("decimal(5,2)")
+                        .HasComment("수수료(%)");
+
+                    b.Property<string>("SID")
+                        .IsRequired()
+                        .HasMaxLength(24)
+                        .HasColumnType("nvarchar(24)")
+                        .HasComment("채널 ID");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("YouTubeUrl")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("YouTube 채널 URL");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Handle")
+                        .IsUnique()
+                        .HasFilter("[Handle] IS NOT NULL");
+
+                    b.HasIndex("MemberID")
+                        .IsUnique();
+
+                    b.HasIndex("Name")
+                        .IsUnique();
+
+                    b.HasIndex("SID")
+                        .IsUnique();
+
+                    b.HasIndex("YouTubeUrl")
+                        .IsUnique();
+
+                    b.HasIndex("MemberID", "IsActive");
+
+                    b.HasIndex("MemberID", "IsVerified");
+
+                    b.HasIndex("MemberID", "IsVerified", "IsActive");
+
+                    b.ToTable("Channel", null, t =>
+                        {
+                            t.HasComment("채널 정보");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberEmailChangeLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("AfterEmail")
+                        .IsRequired()
+                        .HasMaxLength(60)
+                        .HasColumnType("nvarchar(60)")
+                        .HasComment("바뀐 이메일");
+
+                    b.Property<string>("BeforeEmail")
+                        .HasMaxLength(60)
+                        .HasColumnType("nvarchar(60)")
+                        .HasComment("이전 이메일");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Referer")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("이전 페이지 주소");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("MemberID");
+
+                    b.ToTable("MemberEmailChangeLog", null, t =>
+                        {
+                            t.HasComment("사용자 이메일 변경 내역");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberIntroChangeLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("AfterIntro")
+                        .HasMaxLength(3000)
+                        .HasColumnType("nvarchar(3000)")
+                        .HasComment("바꾼 자기소개");
+
+                    b.Property<string>("BeforeIntro")
+                        .HasMaxLength(3000)
+                        .HasColumnType("nvarchar(3000)")
+                        .HasComment("이전 자기소개");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Referer")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("이전 페이지 주소");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.ToTable("MemberIntroChangeLog", null, t =>
+                        {
+                            t.HasComment("자기소개 변경 내역");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberLoginLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Account")
+                        .IsRequired()
+                        .HasMaxLength(120)
+                        .HasColumnType("nvarchar(120)")
+                        .HasComment("로그인 시도한 계정");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP Address");
+
+                    b.Property<int?>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Reason")
+                        .HasMaxLength(225)
+                        .HasColumnType("nvarchar(225)")
+                        .HasComment("실패 이유");
+
+                    b.Property<string>("Referer")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("이전 페이지 주소");
+
+                    b.Property<bool>("Success")
+                        .HasColumnType("bit")
+                        .HasComment("로그인 성공 여부 (0: 실패, 1: 성공)");
+
+                    b.Property<string>("Url")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("요청 주소");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Account");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("MemberID", "Success");
+
+                    b.ToTable("MemberLoginLog", null, t =>
+                        {
+                            t.HasComment("로그인 기록");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberNameChangeLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("AfterName")
+                        .HasMaxLength(40)
+                        .HasColumnType("nvarchar(40)")
+                        .HasComment("바꾼 별명");
+
+                    b.Property<string>("BeforeName")
+                        .HasMaxLength(40)
+                        .HasColumnType("nvarchar(40)")
+                        .HasComment("이전 별명");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Referer")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("이전 페이지 주소");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.ToTable("MemberNameChangeLog", null, t =>
+                        {
+                            t.HasComment("별명 변경 내역");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberSummaryChangeLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("AfterSummary")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("바꾼 한마디");
+
+                    b.Property<string>("BeforeSummary")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("이전 한마디");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("IP Address");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<string>("Referer")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("이전 페이지 주소");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.ToTable("MemberSummaryChangeLog", null, t =>
+                        {
+                            t.HasComment("한마디 변경 내역");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Member", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime?>("AuthCertifiedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("본인인증 일시");
+
+                    b.Property<DateOnly?>("Birthday")
+                        .HasColumnType("date")
+                        .HasComment("생년월일");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("가입 일시");
+
+                    b.Property<DateTime?>("DeletedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("탈퇴 일시");
+
+                    b.Property<DateTime?>("DeniedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("차단 일시");
+
+                    b.Property<string>("DeviceInfo")
+                        .HasMaxLength(400)
+                        .HasColumnType("nvarchar(400)")
+                        .HasComment("로그인 단말기 정보");
+
+                    b.Property<string>("Email")
+                        .IsRequired()
+                        .HasMaxLength(60)
+                        .HasColumnType("nvarchar(60)")
+                        .HasComment("이메일");
+
+                    b.Property<DateTime?>("EmailVerifiedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("이메일 인증 일시");
+
+                    b.Property<string>("FirstName")
+                        .HasMaxLength(20)
+                        .HasColumnType("nvarchar(20)")
+                        .HasComment("본명(성)");
+
+                    b.Property<string>("FullName")
+                        .HasMaxLength(40)
+                        .HasColumnType("nvarchar(40)")
+                        .HasComment("본명");
+
+                    b.Property<int?>("Gender")
+                        .HasColumnType("int")
+                        .HasComment("성별");
+
+                    b.Property<string>("Icon")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("아이콘");
+
+                    b.Property<string>("Intro")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("자기소개");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP Address");
+
+                    b.Property<bool>("IsAdmin")
+                        .HasColumnType("bit")
+                        .HasComment("운영진 여부");
+
+                    b.Property<bool>("IsAuthCertified")
+                        .HasColumnType("bit")
+                        .HasComment("본인 인증 여부");
+
+                    b.Property<bool>("IsCreator")
+                        .HasColumnType("bit")
+                        .HasComment("크리에이터 여부");
+
+                    b.Property<bool>("IsDenied")
+                        .HasColumnType("bit")
+                        .HasComment("차단 여부");
+
+                    b.Property<bool>("IsEmailVerified")
+                        .HasColumnType("bit")
+                        .HasComment("이메일 인증 여부");
+
+                    b.Property<bool>("IsWithdraw")
+                        .HasColumnType("bit")
+                        .HasComment("탈퇴 여부");
+
+                    b.Property<DateTime?>("LastEmailChangedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 이메일 변경 일시");
+
+                    b.Property<DateTime?>("LastIntroChangedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 자기소개 변경 일시");
+
+                    b.Property<DateTime?>("LastLoginAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 로그인 일시");
+
+                    b.Property<string>("LastLoginIp")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("마지막 로그인 IP");
+
+                    b.Property<string>("LastName")
+                        .HasMaxLength(40)
+                        .HasColumnType("nvarchar(40)")
+                        .HasComment("본명(이름)");
+
+                    b.Property<DateTime?>("LastNameChangedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 별명 변경 일시");
+
+                    b.Property<DateTime?>("LastSummaryChangedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("마지막 한마디 변경 일시");
+
+                    b.Property<int?>("MemberGradeID")
+                        .HasColumnType("int")
+                        .HasComment("회원등급 PK");
+
+                    b.Property<string>("Name")
+                        .HasMaxLength(20)
+                        .HasColumnType("nvarchar(20)")
+                        .HasComment("별명");
+
+                    b.Property<string>("Password")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("비밀번호");
+
+                    b.Property<DateTime>("PasswordUpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("비밀번호 변경 일시");
+
+                    b.Property<string>("Phone")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("연락처");
+
+                    b.Property<string>("SID")
+                        .IsRequired()
+                        .HasMaxLength(20)
+                        .HasColumnType("nvarchar(20)")
+                        .HasComment("SID");
+
+                    b.Property<string>("SignupIP")
+                        .HasMaxLength(15)
+                        .HasColumnType("nvarchar(15)")
+                        .HasComment("회원가입 시 IP");
+
+                    b.Property<string>("Summary")
+                        .HasMaxLength(50)
+                        .HasColumnType("nvarchar(50)")
+                        .HasComment("한마디");
+
+                    b.Property<string>("Thumb")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("썸네일");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("User-agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("DeletedAt");
+
+                    b.HasIndex("Email")
+                        .IsUnique();
+
+                    b.HasIndex("FullName");
+
+                    b.HasIndex("Gender");
+
+                    b.HasIndex("IsAdmin");
+
+                    b.HasIndex("IsAuthCertified");
+
+                    b.HasIndex("IsCreator");
+
+                    b.HasIndex("IsDenied");
+
+                    b.HasIndex("IsEmailVerified");
+
+                    b.HasIndex("IsWithdraw");
+
+                    b.HasIndex("MemberGradeID");
+
+                    b.HasIndex("Name")
+                        .IsUnique()
+                        .HasFilter("[Name] IS NOT NULL");
+
+                    b.HasIndex("Phone");
+
+                    b.HasIndex("SID")
+                        .IsUnique();
+
+                    b.ToTable("Member", null, t =>
+                        {
+                            t.HasComment("회원 정보");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.MemberApprove", b =>
+                {
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<DateTime?>("DisclosureInvestConsentAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("투자 현황 공개 동의 일시");
+
+                    b.Property<bool>("IsDisclosureInvest")
+                        .HasColumnType("bit")
+                        .HasComment("투자 현황 공개 여부");
+
+                    b.Property<bool>("IsReceiveEmail")
+                        .HasColumnType("bit")
+                        .HasComment("E-MAIL 수신 여부");
+
+                    b.Property<bool>("IsReceiveNote")
+                        .HasColumnType("bit")
+                        .HasComment("쪽지 수신 여부");
+
+                    b.Property<bool>("IsReceiveSMS")
+                        .HasColumnType("bit")
+                        .HasComment("SMS 수신 여부");
+
+                    b.Property<DateTime?>("ReceiveEmailConsentAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("E-MAIL 수신 동의 일시");
+
+                    b.Property<DateTime?>("ReceiveNoteConsentAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("쪽지 수신 동의 일시");
+
+                    b.Property<DateTime?>("ReceiveSMSConsentAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("SMS 수신 동의 일시");
+
+                    b.HasKey("MemberID");
+
+                    b.ToTable("MemberApprove", null, t =>
+                        {
+                            t.HasComment("회원 동의 및 수신 여부");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.MemberGrade", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Description")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("설명");
+
+                    b.Property<string>("EngName")
+                        .IsRequired()
+                        .HasMaxLength(120)
+                        .HasColumnType("nvarchar(120)")
+                        .HasComment("영문 명");
+
+                    b.Property<string>("Image")
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)")
+                        .HasComment("이미지");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<string>("KorName")
+                        .IsRequired()
+                        .HasMaxLength(240)
+                        .HasColumnType("nvarchar(240)")
+                        .HasComment("한글 명");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<long>("RequiredAttendance")
+                        .HasColumnType("bigint")
+                        .HasComment("누적 출석 수");
+
+                    b.Property<int>("RequiredExp")
+                        .HasColumnType("int")
+                        .HasComment("누적 경험치");
+
+                    b.Property<string>("TextColor")
+                        .HasMaxLength(7)
+                        .HasColumnType("nvarchar(7)")
+                        .HasComment("표시 색상");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("EngName")
+                        .IsUnique();
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("KorName")
+                        .IsUnique();
+
+                    b.HasIndex("Order");
+
+                    b.HasIndex("Order", "IsActive");
+
+                    b.ToTable("MemberGrade", null, t =>
+                        {
+                            t.HasComment("회원 등급");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.MemberStats", b =>
+                {
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int")
+                        .HasComment("회원 ID");
+
+                    b.Property<long>("AttendanceCount")
+                        .HasColumnType("bigint")
+                        .HasComment("출석");
+
+                    b.Property<long>("BookmarkGivenCount")
+                        .HasColumnType("bigint")
+                        .HasComment("즐겨찾기 글 수");
+
+                    b.Property<long>("CommentCount")
+                        .HasColumnType("bigint")
+                        .HasComment("작성 댓글");
+
+                    b.Property<long>("Exp")
+                        .HasColumnType("bigint")
+                        .HasComment("경험치");
+
+                    b.Property<long>("FollowerCount")
+                        .HasColumnType("bigint")
+                        .HasComment("구독자");
+
+                    b.Property<long>("FollowingCount")
+                        .HasColumnType("bigint")
+                        .HasComment("구독 중");
+
+                    b.Property<long>("LikeGivenCount")
+                        .HasColumnType("bigint")
+                        .HasComment("누른 좋아요 수");
+
+                    b.Property<long>("LikeReceivedCount")
+                        .HasColumnType("bigint")
+                        .HasComment("받은 좋아요 수");
+
+                    b.Property<long>("LoginCount")
+                        .HasColumnType("bigint")
+                        .HasComment("로그인");
+
+                    b.Property<long>("PaymentCount")
+                        .HasColumnType("bigint")
+                        .HasComment("결제 횟수");
+
+                    b.Property<long>("PostCount")
+                        .HasColumnType("bigint")
+                        .HasComment("작성 게시글");
+
+                    b.Property<long>("ReportedCount")
+                        .HasColumnType("bigint")
+                        .HasComment("신고 당한 횟수");
+
+                    b.Property<byte[]>("RowVersion")
+                        .IsConcurrencyToken()
+                        .IsRequired()
+                        .ValueGeneratedOnAddOrUpdate()
+                        .HasColumnType("rowversion")
+                        .HasComment("동시성");
+
+                    b.Property<int>("SuspensionCount")
+                        .HasColumnType("int")
+                        .HasComment("정지 횟수");
+
+                    b.Property<long>("TotalCanceledAmount")
+                        .HasColumnType("bigint")
+                        .HasComment("누적 취소/환불 금액");
+
+                    b.Property<long>("TotalPaidAmount")
+                        .HasColumnType("bigint")
+                        .HasComment("누적 결제 금액");
+
+                    b.Property<int>("WarningCount")
+                        .HasColumnType("int")
+                        .HasComment("경고 횟수");
+
+                    b.HasKey("MemberID");
+
+                    b.ToTable("MemberStats", null, t =>
+                        {
+                            t.HasComment("회원 활동 집계");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Banner.BannerItem", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("DesktopImage")
+                        .HasMaxLength(1024)
+                        .HasColumnType("nvarchar(1024)")
+                        .HasComment("이미지(Desktop)");
+
+                    b.Property<DateTime?>("EndAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("사용 기간 - 종료");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<string>("Link")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("주소");
+
+                    b.Property<string>("MobileImage")
+                        .HasMaxLength(1024)
+                        .HasColumnType("nvarchar(1024)")
+                        .HasComment("이미지(Mobile)");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<int>("PositionID")
+                        .HasColumnType("int")
+                        .HasComment("배너 위치 ID");
+
+                    b.Property<DateTime?>("StartAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("사용 기간 - 시작");
+
+                    b.Property<string>("Subject")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("배너 명");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("Order");
+
+                    b.HasIndex("PositionID");
+
+                    b.HasIndex("PositionID", "Order", "IsActive");
+
+                    b.ToTable("BannerItem", null, t =>
+                        {
+                            t.HasComment("배너 아이템");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Banner.BannerPosition", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Code")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("위치 구분");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<string>("Subject")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("위치 명");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("Code", "IsActive");
+
+                    b.ToTable("BannerPosition", null, t =>
+                        {
+                            t.HasComment("배너 위치");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Document", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Code")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("주소");
+
+                    b.Property<string>("Content")
+                        .HasMaxLength(5000)
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("내용");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<string>("Subject")
+                        .IsRequired()
+                        .HasMaxLength(120)
+                        .HasColumnType("nvarchar(120)")
+                        .HasComment("제목");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<int>("Views")
+                        .HasColumnType("int")
+                        .HasComment("조회 수");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("Subject");
+
+                    b.HasIndex("Code", "IsActive");
+
+                    b.ToTable("Document", null, t =>
+                        {
+                            t.HasComment("문서");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Faq.FaqCategory", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Code")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("주소");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<string>("Subject")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("분류 명");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("Order", "IsActive");
+
+                    b.HasIndex("Code", "Order", "IsActive");
+
+                    b.ToTable("FaqCategory", null, t =>
+                        {
+                            t.HasComment("FAQ 분류");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Faq.FaqItem", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Answer")
+                        .HasMaxLength(4000)
+                        .HasColumnType("nvarchar(4000)")
+                        .HasComment("답변");
+
+                    b.Property<int>("CategoryID")
+                        .HasColumnType("int")
+                        .HasComment("분류 ID");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<string>("Question")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("질문");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CategoryID");
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("Order");
+
+                    b.HasIndex("Order", "IsActive");
+
+                    b.HasIndex("CategoryID", "Order", "IsActive");
+
+                    b.ToTable("FaqItem", null, t =>
+                        {
+                            t.HasComment("FAQ 목록");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Popup", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Content")
+                        .HasMaxLength(4000)
+                        .HasColumnType("nvarchar(4000)")
+                        .HasComment("내용");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<DateTime?>("EndAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("사용 기간 - 종료");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<string>("Link")
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("주소");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<DateTime?>("StartAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("사용 기간 - 시작");
+
+                    b.Property<string>("Subject")
+                        .IsRequired()
+                        .HasMaxLength(255)
+                        .HasColumnType("nvarchar(255)")
+                        .HasComment("제목");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Order");
+
+                    b.HasIndex("Order", "IsActive");
+
+                    b.HasIndex("StartAt", "EndAt", "Order", "IsActive");
+
+                    b.ToTable("Popup", null, t =>
+                        {
+                            t.HasComment("팝업");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.Wallet", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<Guid>("WalletKey")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID")
+                        .IsUnique();
+
+                    b.HasIndex("WalletKey")
+                        .IsUnique();
+
+                    b.ToTable("Wallet", null, t =>
+                        {
+                            t.HasComment("회원 지갑");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.WalletBalance", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("Type")
+                        .HasColumnType("int");
+
+                    b.Property<Guid>("WalletKey")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("WalletKey", "Type")
+                        .IsUnique();
+
+                    b.ToTable("WalletBalance", null, t =>
+                        {
+                            t.HasComment("회원 지갑 잔액");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.WalletTransaction", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("BalanceType")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("Memo")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)");
+
+                    b.Property<string>("Reason")
+                        .IsRequired()
+                        .HasMaxLength(1000)
+                        .HasColumnType("nvarchar(1000)");
+
+                    b.Property<string>("RefID")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)");
+
+                    b.Property<int>("TxType")
+                        .HasColumnType("int");
+
+                    b.Property<string>("UserID")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)");
+
+                    b.Property<Guid>("WalletKey")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("WalletKey");
+
+                    b.HasIndex("WalletKey", "CreatedAt");
+
+                    b.ToTable("WalletTransaction", null, t =>
+                        {
+                            t.HasComment("회원 거래 장부");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Common.Config", b =>
+                {
+                    b.OwnsOne("Domain.Entities.Common.AccountConfig", "Account", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<int?>("ChangeEmailDay")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_ChangeEmailDay")
+                                .HasComment("이메일 갱신 주기(일)");
+
+                            b1.Property<int?>("ChangeIntroDay")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_ChangeIntroDay")
+                                .HasComment("자기소개 갱신 주기(일)");
+
+                            b1.Property<int?>("ChangeNameDay")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_ChangeNameDay")
+                                .HasComment("별명 갱신 주기(일)");
+
+                            b1.Property<int?>("ChangePasswordDay")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_ChangePasswordDay")
+                                .HasComment("비밀번호 갱신 주기(일)");
+
+                            b1.Property<int?>("ChangeSummaryDay")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_ChangeSummaryDay")
+                                .HasComment("한마디 갱신 주기(일)");
+
+                            b1.Property<string>("DeniedEmailList")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Account_DeniedEmailList")
+                                .HasComment("금지 이메일");
+
+                            b1.Property<string>("DeniedNameList")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Account_DeniedNameList")
+                                .HasComment("금지 별명");
+
+                            b1.Property<bool>("IsRegisterBlock")
+                                .HasColumnType("bit")
+                                .HasColumnName("Account_IsRegisterBlock")
+                                .HasComment("회원가입 차단");
+
+                            b1.Property<bool>("IsRegisterEmailAuth")
+                                .HasColumnType("bit")
+                                .HasColumnName("Account_IsRegisterEmailAuth")
+                                .HasComment("회원가입 시 이메일 인증");
+
+                            b1.Property<int?>("MaxLoginTryCount")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_MaxLoginTryCount")
+                                .HasComment("로그인 시도 제한 횟수");
+
+                            b1.Property<int?>("MaxLoginTryLimitSecond")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_MaxLoginTryLimitSecond")
+                                .HasComment("로그인 시도 제한 시간(초)");
+
+                            b1.Property<int?>("PasswordMinLength")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_PasswordMinLength")
+                                .HasComment("비밀번호 최소 길이");
+
+                            b1.Property<int?>("PasswordNumbersLength")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_PasswordNumbersLength")
+                                .HasComment("비밀번호 최소 숫자 수");
+
+                            b1.Property<int?>("PasswordSpecialcharsLength")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_PasswordSpecialcharsLength")
+                                .HasComment("비밀번호 최소 특수문자 수");
+
+                            b1.Property<int?>("PasswordUppercaseLength")
+                                .HasColumnType("int")
+                                .HasColumnName("Account_PasswordUppercaseLength")
+                                .HasComment("비밀번호 최소 대문자 수");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.BasicConfig", "Basic", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("AdminWhiteIPList")
+                                .HasMaxLength(1000)
+                                .HasColumnType("nvarchar(1000)")
+                                .HasColumnName("Basic_AdminWhiteIPList")
+                                .HasComment("관리자단 접근 가능 IP");
+
+                            b1.Property<string>("BlockAlertContent")
+                                .HasMaxLength(5000)
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Basic_BlockAlertContent")
+                                .HasComment("차단 시 안내문 내용");
+
+                            b1.Property<string>("BlockAlertTitle")
+                                .HasMaxLength(200)
+                                .HasColumnType("nvarchar(200)")
+                                .HasColumnName("Basic_BlockAlertTitle")
+                                .HasComment("차단 시 안내문 제목");
+
+                            b1.Property<string>("FromEmail")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Basic_FromEmail")
+                                .HasComment("송수신 이메일");
+
+                            b1.Property<string>("FromName")
+                                .HasMaxLength(30)
+                                .HasColumnType("nvarchar(30)")
+                                .HasColumnName("Basic_FromName")
+                                .HasComment("송수신자 이름");
+
+                            b1.Property<string>("FrontWhiteIPList")
+                                .HasMaxLength(1000)
+                                .HasColumnType("nvarchar(1000)")
+                                .HasColumnName("Basic_FrontWhiteIPList")
+                                .HasComment("사용자단 접근 가능 IP");
+
+                            b1.Property<bool>("IsMaintenance")
+                                .HasColumnType("bit")
+                                .HasColumnName("Basic_IsMaintenance")
+                                .HasComment("점검 여부");
+
+                            b1.Property<string>("MaintenanceContent")
+                                .HasMaxLength(5000)
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Basic_MaintenanceContent")
+                                .HasComment("점검 내용");
+
+                            b1.Property<string>("RootID")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Basic_RootID")
+                                .HasComment("최고 관리자 ID");
+
+                            b1.Property<string>("SiteName")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Basic_SiteName")
+                                .HasComment("사이트 이름");
+
+                            b1.Property<string>("SiteURL")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Basic_SiteURL")
+                                .HasComment("사이트 주소");
+
+                            b1.Property<bool>("SmtpEnableSSL")
+                                .HasColumnType("bit")
+                                .HasColumnName("Basic_SmtpEnableSSL")
+                                .HasComment("SMTP Enable SSL");
+
+                            b1.Property<string>("SmtpPassword")
+                                .HasMaxLength(200)
+                                .HasColumnType("nvarchar(200)")
+                                .HasColumnName("Basic_SmtpPassword")
+                                .HasComment("SMTP Password");
+
+                            b1.Property<int?>("SmtpPort")
+                                .HasColumnType("int")
+                                .HasColumnName("Basic_SmtpPort")
+                                .HasComment("SMTP Port");
+
+                            b1.Property<string>("SmtpServer")
+                                .HasMaxLength(200)
+                                .HasColumnType("nvarchar(200)")
+                                .HasColumnName("Basic_SmtpServer")
+                                .HasComment("SMTP Server");
+
+                            b1.Property<string>("SmtpUsername")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Basic_SmtpUsername")
+                                .HasComment("SMTP Username");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.CompanyConfig", "Company", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("AddedSaleNo")
+                                .HasMaxLength(20)
+                                .HasColumnType("nvarchar(20)")
+                                .HasColumnName("Company_AddedSaleNo")
+                                .HasComment("부가통신 사업자번호");
+
+                            b1.Property<string>("Address")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Company_Address")
+                                .HasComment("사업장 소재지");
+
+                            b1.Property<string>("AdminEmail")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Company_AdminEmail")
+                                .HasComment("정보관리책임자 이메일");
+
+                            b1.Property<string>("AdminName")
+                                .HasMaxLength(70)
+                                .HasColumnType("nvarchar(70)")
+                                .HasColumnName("Company_AdminName")
+                                .HasComment("정보관리책임자");
+
+                            b1.Property<string>("BankCode")
+                                .HasMaxLength(10)
+                                .HasColumnType("nvarchar(10)")
+                                .HasColumnName("Company_BankCode")
+                                .HasComment("입금계좌 - 은행");
+
+                            b1.Property<string>("BankNumber")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Company_BankNumber")
+                                .HasComment("입금계좌 - 계좌번호");
+
+                            b1.Property<string>("BankOwner")
+                                .HasMaxLength(70)
+                                .HasColumnType("nvarchar(70)")
+                                .HasColumnName("Company_BankOwner")
+                                .HasComment("입금계좌 - 예금주");
+
+                            b1.Property<string>("Fax")
+                                .HasMaxLength(20)
+                                .HasColumnType("nvarchar(20)")
+                                .HasColumnName("Company_Fax")
+                                .HasComment("FAX");
+
+                            b1.Property<string>("Hosting")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Company_Hosting")
+                                .HasComment("호스팅 서비스");
+
+                            b1.Property<string>("Name")
+                                .HasMaxLength(70)
+                                .HasColumnType("nvarchar(70)")
+                                .HasColumnName("Company_Name")
+                                .HasComment("상호 명");
+
+                            b1.Property<string>("Owner")
+                                .HasMaxLength(50)
+                                .HasColumnType("nvarchar(50)")
+                                .HasColumnName("Company_Owner")
+                                .HasComment("대표자 명");
+
+                            b1.Property<string>("RegNo")
+                                .HasMaxLength(100)
+                                .HasColumnType("nvarchar(100)")
+                                .HasColumnName("Company_RegNo")
+                                .HasComment("사업자 등록 번호");
+
+                            b1.Property<string>("RetailSaleNo")
+                                .HasMaxLength(20)
+                                .HasColumnType("nvarchar(20)")
+                                .HasColumnName("Company_RetailSaleNo")
+                                .HasComment("통신판매업 신고번호");
+
+                            b1.Property<string>("SiteUrl")
+                                .HasMaxLength(200)
+                                .HasColumnType("nvarchar(200)")
+                                .HasColumnName("Company_SiteUrl")
+                                .HasComment("사이트 주소");
+
+                            b1.Property<string>("Tel")
+                                .HasMaxLength(20)
+                                .HasColumnType("nvarchar(20)")
+                                .HasColumnName("Company_Tel")
+                                .HasComment("대표 전화번호");
+
+                            b1.Property<string>("ZipCode")
+                                .HasMaxLength(8)
+                                .HasColumnType("nvarchar(8)")
+                                .HasColumnName("Company_ZipCode")
+                                .HasComment("사업장 주소(우편번호)");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.EmailTemplateConfig", "EmailTemplate", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("ChangedEmailFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_ChangedEmailFormContent")
+                                .HasComment("이메일 변경 완료 - 내용");
+
+                            b1.Property<string>("ChangedEmailFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_ChangedEmailFormTitle")
+                                .HasComment("이메일 변경 완료 - 제목");
+
+                            b1.Property<string>("ChangedPasswordEmailFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_ChangedPasswordEmailFormContent")
+                                .HasComment("비밀번호 변경 완료 - 내용");
+
+                            b1.Property<string>("ChangedPasswordEmailFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_ChangedPasswordEmailFormTitle")
+                                .HasComment("비밀번호 변경 완료 - 제목");
+
+                            b1.Property<string>("EmailVerifyFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_EmailVerifyFormContent")
+                                .HasComment("이메일 변경 시 - 내용");
+
+                            b1.Property<string>("EmailVerifyFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_EmailVerifyFormTitle")
+                                .HasComment("이메일 변경 시 - 제목");
+
+                            b1.Property<string>("RegisterEmailFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_RegisterEmailFormContent")
+                                .HasComment("회원가입 시 - 내용");
+
+                            b1.Property<string>("RegisterEmailFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_RegisterEmailFormTitle")
+                                .HasComment("회원가입 시 - 제목");
+
+                            b1.Property<string>("RegistrationEmailFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_RegistrationEmailFormContent")
+                                .HasComment("회원가입 완료 - 내용");
+
+                            b1.Property<string>("RegistrationEmailFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_RegistrationEmailFormTitle")
+                                .HasComment("회원가입 완료 - 제목");
+
+                            b1.Property<string>("ResetPasswordEmailFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_ResetPasswordEmailFormContent")
+                                .HasComment("비밀번호 재설정 - 내용");
+
+                            b1.Property<string>("ResetPasswordEmailFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_ResetPasswordEmailFormTitle")
+                                .HasComment("비밀번호 재설정 - 제목");
+
+                            b1.Property<string>("WithdrawEmailFormContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_WithdrawEmailFormContent")
+                                .HasComment("회원탈퇴 시 - 내용");
+
+                            b1.Property<string>("WithdrawEmailFormTitle")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("EmailTemplate_WithdrawEmailFormTitle")
+                                .HasComment("회원탈퇴 시 - 제목");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.ExternalApiConfig", "External", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("GoogleAppId")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("External_GoogleAppId")
+                                .HasComment("Google APP ID");
+
+                            b1.Property<string>("GoogleClientId")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("External_GoogleClientId")
+                                .HasComment("Google Client ID");
+
+                            b1.Property<string>("GoogleClientSecretEnc")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("External_GoogleClientSecretEnc")
+                                .HasComment("Google Client Secret (암호화 저장 권장)");
+
+                            b1.Property<string>("YouTubeApiKeyEnc")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("External_YouTubeApiKeyEnc")
+                                .HasComment("YouTube API Key (암호화 저장 권장)");
+
+                            b1.Property<string>("YouTubeApiName")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("External_YouTubeApiName")
+                                .HasComment("YouTube API Name");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.ImagesConfig", "Images", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("AppIcon_192")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_AppIcon_192")
+                                .HasComment("App-icon-192");
+
+                            b1.Property<string>("AppIcon_512")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_AppIcon_512")
+                                .HasComment("App-icon-512");
+
+                            b1.Property<string>("AppleTouchIcon")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_AppleTouchIcon")
+                                .HasComment("Apple-touch-icon");
+
+                            b1.Property<string>("Favicon")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_Favicon")
+                                .HasComment("Favicon");
+
+                            b1.Property<string>("LogoHorizontal")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_LogoHorizontal")
+                                .HasComment("Logo-horizontal");
+
+                            b1.Property<string>("LogoSquare")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_LogoSquare")
+                                .HasComment("Logo-square");
+
+                            b1.Property<string>("OgDefault")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_OgDefault")
+                                .HasComment("og-default");
+
+                            b1.Property<string>("TwitterImage")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Images_TwitterImage")
+                                .HasComment("Twitter-image");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.MetaConfig", "Meta", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("Adds")
+                                .HasColumnType("nvarchar(max)");
+
+                            b1.Property<string>("ApplicationName")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_ApplicationName")
+                                .HasComment("Meta Application Name");
+
+                            b1.Property<string>("Author")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_Author")
+                                .HasComment("Meta Author");
+
+                            b1.Property<string>("Description")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_Description")
+                                .HasComment("Meta Description");
+
+                            b1.Property<string>("Generator")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_Generator")
+                                .HasComment("Meta Generator");
+
+                            b1.Property<string>("Keywords")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_Keywords")
+                                .HasComment("Meta Keywords");
+
+                            b1.Property<string>("Robots")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_Robots")
+                                .HasComment("Meta Robots");
+
+                            b1.Property<string>("Viewport")
+                                .HasMaxLength(255)
+                                .HasColumnType("nvarchar(255)")
+                                .HasColumnName("Meta_Viewport")
+                                .HasComment("Meta Viewport");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.PaymentConfig", "Payment", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.HasKey("ConfigID");
+
+                            b1.ToTable("Config");
+
+                            b1.WithOwner()
+                                .HasForeignKey("ConfigID");
+                        });
+
+                    b.Navigation("Account")
+                        .IsRequired();
+
+                    b.Navigation("Basic")
+                        .IsRequired();
+
+                    b.Navigation("Company")
+                        .IsRequired();
+
+                    b.Navigation("EmailTemplate")
+                        .IsRequired();
+
+                    b.Navigation("External")
+                        .IsRequired();
+
+                    b.Navigation("Images")
+                        .IsRequired();
+
+                    b.Navigation("Meta")
+                        .IsRequired();
+
+                    b.Navigation("Payment")
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.Board", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.BoardGroup", "BoardGroup")
+                        .WithMany("Board")
+                        .HasForeignKey("BoardGroupID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("BoardGroup");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardManager", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany("BoardManager")
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardMeta", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", null)
+                        .WithOne("BoardMeta")
+                        .HasForeignKey("Domain.Entities.Forum.Boards.BoardMeta", "BoardID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaComment", "Comment", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<bool>("AllowDeleteProtection")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_AllowDeleteProtection");
+
+                            b1.Property<bool>("AllowDisLike")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_AllowDisLike");
+
+                            b1.Property<bool>("AllowLike")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_AllowLike");
+
+                            b1.Property<bool>("AllowSecret")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_AllowSecret");
+
+                            b1.Property<bool>("AllowUpdateProtection")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_AllowUpdateProtection");
+
+                            b1.Property<int>("BlameHideCount")
+                                .HasColumnType("int")
+                                .HasColumnName("Comment_BlameHideCount");
+
+                            b1.Property<string>("ContentPlaceholder")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Comment_ContentPlaceholder");
+
+                            b1.Property<int>("DeleteProtectionDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Comment_DeleteProtectionDays");
+
+                            b1.Property<bool>("EnableComment")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_EnableComment");
+
+                            b1.Property<bool>("EnableCommentUpdateLog")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_EnableCommentUpdateLog");
+
+                            b1.Property<bool>("EnableEditor")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_EnableEditor");
+
+                            b1.Property<int>("MaxContentLength")
+                                .HasColumnType("int")
+                                .HasColumnName("Comment_MaxContentLength");
+
+                            b1.Property<int>("MinContentLength")
+                                .HasColumnType("int")
+                                .HasColumnName("Comment_MinContentLength");
+
+                            b1.Property<int>("PerPage")
+                                .HasColumnType("int")
+                                .HasColumnName("Comment_PerPage");
+
+                            b1.Property<bool>("ShowMemberIcon")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_ShowMemberIcon");
+
+                            b1.Property<bool>("ShowMemberPhoto")
+                                .HasColumnType("bit")
+                                .HasColumnName("Comment_ShowMemberPhoto");
+
+                            b1.Property<int>("UpdateProtectionDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Comment_UpdateProtectionDays");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaExp", "Exp", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<int>("CommentWriteExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_CommentWriteExp");
+
+                            b1.Property<int>("CommentWriteExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_CommentWriteExpWithinDays");
+
+                            b1.Property<int>("CommentWriteUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_CommentWriteUndoExp");
+
+                            b1.Property<bool>("EnableExp")
+                                .HasColumnType("bit")
+                                .HasColumnName("Exp_EnableExp");
+
+                            b1.Property<short>("FileDownloadExp")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Exp_FileDownloadExp");
+
+                            b1.Property<int>("FileUploadExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_FileUploadExp");
+
+                            b1.Property<int>("FileUploadExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_FileUploadExpWithinDays");
+
+                            b1.Property<int>("FileUploadUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_FileUploadUndoExp");
+
+                            b1.Property<int>("OtherCommentDisLikeExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherCommentDisLikeExp");
+
+                            b1.Property<int>("OtherCommentDisLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherCommentDisLikeExpWithinDays");
+
+                            b1.Property<int>("OtherCommentDisLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherCommentDisLikeUndoExp");
+
+                            b1.Property<int>("OtherCommentLikeExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherCommentLikeExp");
+
+                            b1.Property<int>("OtherCommentLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherCommentLikeExpWithinDays");
+
+                            b1.Property<int>("OtherCommentLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherCommentLikeUndoExp");
+
+                            b1.Property<int>("OtherPostDisLikeExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostDisLikeExp");
+
+                            b1.Property<int>("OtherPostDisLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostDisLikeExpWithinDays");
+
+                            b1.Property<int>("OtherPostDisLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostDisLikeUndoExp");
+
+                            b1.Property<int>("OtherPostLikeExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostLikeExp");
+
+                            b1.Property<int>("OtherPostLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostLikeExpWithinDays");
+
+                            b1.Property<int>("OtherPostLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostLikeUndoExp");
+
+                            b1.Property<short>("OtherPostReadExp")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Exp_OtherPostReadExp");
+
+                            b1.Property<int>("OtherPostReadExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostReadExpWithinDays");
+
+                            b1.Property<int>("OtherPostReadUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OtherPostReadUndoExp");
+
+                            b1.Property<short>("OwnCommentDisLikeExp")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Exp_OwnCommentDisLikeExp");
+
+                            b1.Property<int>("OwnCommentDisLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnCommentDisLikeExpWithinDays");
+
+                            b1.Property<int>("OwnCommentDisLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnCommentDisLikeUndoExp");
+
+                            b1.Property<int>("OwnCommentLikeExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnCommentLikeExp");
+
+                            b1.Property<int>("OwnCommentLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnCommentLikeExpWithinDays");
+
+                            b1.Property<int>("OwnCommentLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnCommentLikeUndoExp");
+
+                            b1.Property<short>("OwnPostDisLikeExp")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Exp_OwnPostDisLikeExp");
+
+                            b1.Property<int>("OwnPostDisLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostDisLikeExpWithinDays");
+
+                            b1.Property<int>("OwnPostDisLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostDisLikeUndoExp");
+
+                            b1.Property<int>("OwnPostLikeExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostLikeExp");
+
+                            b1.Property<int>("OwnPostLikeExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostLikeExpWithinDays");
+
+                            b1.Property<int>("OwnPostLikeUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostLikeUndoExp");
+
+                            b1.Property<int>("OwnPostReadExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostReadExp");
+
+                            b1.Property<int>("OwnPostReadExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostReadExpWithinDays");
+
+                            b1.Property<int>("OwnPostReadUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_OwnPostReadUndoExp");
+
+                            b1.Property<int>("PostWriteExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_PostWriteExp");
+
+                            b1.Property<int>("PostWriteExpWithinDays")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_PostWriteExpWithinDays");
+
+                            b1.Property<int>("PostWriteUndoExp")
+                                .HasColumnType("int")
+                                .HasColumnName("Exp_PostWriteUndoExp");
+
+                            b1.Property<bool>("ShowExpGuide")
+                                .HasColumnType("bit")
+                                .HasColumnName("Exp_ShowExpGuide");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaGeneral", "General", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<bool>("AllowDeleteProtection")
+                                .HasColumnType("bit")
+                                .HasColumnName("General_AllowDeleteProtection");
+
+                            b1.Property<bool>("AllowUpdateProtection")
+                                .HasColumnType("bit")
+                                .HasColumnName("General_AllowUpdateProtection");
+
+                            b1.Property<int>("DeleteProtectionDays")
+                                .HasColumnType("int")
+                                .HasColumnName("General_DeleteProtectionDays");
+
+                            b1.Property<bool>("EnableFileDownLog")
+                                .HasColumnType("bit")
+                                .HasColumnName("General_EnableFileDownLog");
+
+                            b1.Property<bool>("EnableLinkClickLog")
+                                .HasColumnType("bit")
+                                .HasColumnName("General_EnableLinkClickLog");
+
+                            b1.Property<bool>("EnablePostUpdateLog")
+                                .HasColumnType("bit")
+                                .HasColumnName("General_EnablePostUpdateLog");
+
+                            b1.Property<int>("UpdateProtectionDays")
+                                .HasColumnType("int")
+                                .HasColumnName("General_UpdateProtectionDays");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaList", "List", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<bool>("AlwaysShowWriteButton")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_AlwaysShowWriteButton");
+
+                            b1.Property<bool>("ExceptNotice")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_ExceptNotice");
+
+                            b1.Property<bool>("ExceptSpeaker")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_ExceptSpeaker");
+
+                            b1.Property<string>("FooterContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("List_FooterContent");
+
+                            b1.Property<string>("HeaderContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("List_HeaderContent");
+
+                            b1.Property<bool>("IsHotIcon")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_IsHotIcon");
+
+                            b1.Property<bool>("IsNewIcon")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_IsNewIcon");
+
+                            b1.Property<byte?>("Layout")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("List_Layout");
+
+                            b1.Property<byte>("PerPage")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("List_PerPage");
+
+                            b1.Property<bool>("ShowFooter")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_ShowFooter");
+
+                            b1.Property<bool>("ShowFooterListView")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_ShowFooterListView");
+
+                            b1.Property<bool>("ShowHeader")
+                                .HasColumnType("bit")
+                                .HasColumnName("List_ShowHeader");
+
+                            b1.Property<byte?>("Sort")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("List_Sort");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaNotify", "Notify", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<byte?>("CommentWriteNotify")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Notify_CommentWriteNotify");
+
+                            b1.Property<byte?>("PostWriteNotify")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Notify_PostWriteNotify");
+
+                            b1.Property<byte?>("ReplyWriteNotify")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Notify_ReplyWriteNotify");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaNotifyTemplate", "NotifyTemplate", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("CommentWriteEmailNotifyContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("NotifyTemplate_CommentWriteEmailNotifyContent");
+
+                            b1.Property<string>("CommentWriteEmailNotifySubject")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("NotifyTemplate_CommentWriteEmailNotifySubject");
+
+                            b1.Property<string>("PostWriteEmailNotifyContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("NotifyTemplate_PostWriteEmailNotifyContent");
+
+                            b1.Property<string>("PostWriteEmailNotifySubject")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("NotifyTemplate_PostWriteEmailNotifySubject");
+
+                            b1.Property<string>("ReplyWriteEmailNotifyContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("NotifyTemplate_ReplyWriteEmailNotifyContent");
+
+                            b1.Property<string>("ReplyWriteEmailNotifySubject")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("NotifyTemplate_ReplyWriteEmailNotifySubject");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaPermission", "Permission", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<short>("BoardAccess")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_BoardAccess");
+
+                            b1.Property<short>("CommentView")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_CommentView");
+
+                            b1.Property<short>("CommentWrite")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_CommentWrite");
+
+                            b1.Property<short>("FileDownload")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_FileDownload");
+
+                            b1.Property<short>("FileUpload")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_FileUpload");
+
+                            b1.Property<short>("PostView")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_PostView");
+
+                            b1.Property<short>("PostWrite")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_PostWrite");
+
+                            b1.Property<short>("ReplyWrite")
+                                .HasColumnType("smallint")
+                                .HasColumnName("Permission_ReplyWrite");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaView", "View", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<bool>("AllowBlame")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowBlame");
+
+                            b1.Property<bool>("AllowBookmark")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowBookmark");
+
+                            b1.Property<bool>("AllowContentLinkTargetBlank")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowContentLinkTargetBlank");
+
+                            b1.Property<bool>("AllowDislike")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowDislike");
+
+                            b1.Property<bool>("AllowLike")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowLike");
+
+                            b1.Property<bool>("AllowPostUrlCopy")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowPostUrlCopy");
+
+                            b1.Property<bool>("AllowPostUrlQrCode")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowPostUrlQrCode");
+
+                            b1.Property<bool>("AllowPrevNextBotton")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowPrevNextBotton");
+
+                            b1.Property<bool>("AllowPrint")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowPrint");
+
+                            b1.Property<bool>("AllowSnsShare")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_AllowSnsShare");
+
+                            b1.Property<int>("BlameHideCount")
+                                .HasColumnType("int")
+                                .HasColumnName("View_BlameHideCount");
+
+                            b1.Property<bool>("ShowMemberIcon")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_ShowMemberIcon");
+
+                            b1.Property<bool>("ShowMemberPhoto")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_ShowMemberPhoto");
+
+                            b1.Property<bool>("ShowMemberRegDate")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_ShowMemberRegDate");
+
+                            b1.Property<bool>("ShowMemberSummary")
+                                .HasColumnType("bit")
+                                .HasColumnName("View_ShowMemberSummary");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Forum.Boards.BoardMetaWrite", "Write", b1 =>
+                        {
+                            b1.Property<int>("BoardMetaID")
+                                .HasColumnType("int");
+
+                            b1.Property<bool>("AllowEditor")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowEditor");
+
+                            b1.Property<bool>("AllowFile")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowFile");
+
+                            b1.Property<bool>("AllowImage")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowImage");
+
+                            b1.Property<bool>("AllowMedia")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowMedia");
+
+                            b1.Property<bool>("AllowPrefix")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowPrefix");
+
+                            b1.Property<bool>("AllowSecret")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowSecret");
+
+                            b1.Property<bool>("AllowTag")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_AllowTag");
+
+                            b1.Property<string>("DefaultContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Write_DefaultContent");
+
+                            b1.Property<string>("DefaultSubject")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Write_DefaultSubject");
+
+                            b1.Property<string>("FileUploadExtension")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Write_FileUploadExtension");
+
+                            b1.Property<byte>("FileUploadLimit")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Write_FileUploadLimit");
+
+                            b1.Property<int>("FileUploadMaxSize")
+                                .HasColumnType("int")
+                                .HasColumnName("Write_FileUploadMaxSize");
+
+                            b1.Property<string>("FooterContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Write_FooterContent");
+
+                            b1.Property<string>("HeaderContent")
+                                .HasColumnType("nvarchar(max)")
+                                .HasColumnName("Write_HeaderContent");
+
+                            b1.Property<byte>("ImageUploadLimit")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Write_ImageUploadLimit");
+
+                            b1.Property<int>("ImageUploadMaxSize")
+                                .HasColumnType("int")
+                                .HasColumnName("Write_ImageUploadMaxSize");
+
+                            b1.Property<byte>("MediaUploadLimit")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Write_MediaUploadLimit");
+
+                            b1.Property<bool>("RequiredPrefix")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_RequiredPrefix");
+
+                            b1.Property<bool>("ShowFooter")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_ShowFooter");
+
+                            b1.Property<bool>("ShowHeader")
+                                .HasColumnType("bit")
+                                .HasColumnName("Write_ShowHeader");
+
+                            b1.Property<byte>("TagLimit")
+                                .HasColumnType("tinyint")
+                                .HasColumnName("Write_TagLimit");
+
+                            b1.HasKey("BoardMetaID");
+
+                            b1.ToTable("BoardMeta");
+
+                            b1.WithOwner()
+                                .HasForeignKey("BoardMetaID");
+                        });
+
+                    b.Navigation("Comment")
+                        .IsRequired();
+
+                    b.Navigation("Exp")
+                        .IsRequired();
+
+                    b.Navigation("General")
+                        .IsRequired();
+
+                    b.Navigation("List")
+                        .IsRequired();
+
+                    b.Navigation("Notify")
+                        .IsRequired();
+
+                    b.Navigation("NotifyTemplate")
+                        .IsRequired();
+
+                    b.Navigation("Permission")
+                        .IsRequired();
+
+                    b.Navigation("View")
+                        .IsRequired();
+
+                    b.Navigation("Write")
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardPrefix", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany("BoardPrefix")
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.Comment", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "MentionMember")
+                        .WithMany()
+                        .HasForeignKey("MentionMemberID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Parent")
+                        .WithMany("Children")
+                        .HasForeignKey("ParentID")
+                        .OnDelete(DeleteBehavior.Restrict);
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("Comment")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("MentionMember");
+
+                    b.Navigation("Parent");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentFile", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentFile")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentImage", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentImage")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentLink", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentLink")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentMedia", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentMedia")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentMention", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithOne("CommentMention")
+                        .HasForeignKey("Domain.Entities.Forum.Comments.CommentMention", "CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentReaction", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentReaction")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.CommentReport", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentReport")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany()
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.CommentFileDownLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Comments.CommentFile", "CommentFile")
+                        .WithMany()
+                        .HasForeignKey("CommentFileID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentFileDownLog")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Restrict);
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("CommentFile");
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.CommentLinkClickLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentLinkClickLog")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Comments.CommentLink", "CommentLink")
+                        .WithMany()
+                        .HasForeignKey("CommentLinkID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Restrict);
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("CommentLink");
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.CommentUpdateLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Comments.Comment", "Comment")
+                        .WithMany("CommentUpdateLog")
+                        .HasForeignKey("CommentID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Comment");
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.PostFileDownLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Restrict);
+
+                    b.HasOne("Domain.Entities.Forum.Posts.PostFile", "PostFile")
+                        .WithMany()
+                        .HasForeignKey("PostFileID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostFileDownLog")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+
+                    b.Navigation("PostFile");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.PostLinkClickLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Restrict);
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostLinkClickLog")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.PostLink", "PostLink")
+                        .WithMany()
+                        .HasForeignKey("PostLinkID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+
+                    b.Navigation("PostLink");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Logs.PostUpdateLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostUpdateLog")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.Post", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany("Post")
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Boards.BoardPrefix", "BoardPrefix")
+                        .WithMany()
+                        .HasForeignKey("BoardPrefixID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.Navigation("Board");
+
+                    b.Navigation("BoardPrefix");
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostBookmark", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostBookmark")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostFile", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostFile")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostImage", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostImage")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostLink", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostLink")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostMedia", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostMedia")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostReaction", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostReaction")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostReport", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostReport")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Member");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.PostTag", b =>
+                {
+                    b.HasOne("Domain.Entities.Forum.Boards.Board", "Board")
+                        .WithMany()
+                        .HasForeignKey("BoardID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Post", "Post")
+                        .WithMany("PostTag")
+                        .HasForeignKey("PostID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Forum.Posts.Tag", "Tag")
+                        .WithMany("PostTag")
+                        .HasForeignKey("TagID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Board");
+
+                    b.Navigation("Post");
+
+                    b.Navigation("Tag");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Channel", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithOne("Channel")
+                        .HasForeignKey("Domain.Entities.Members.Channel", "MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberEmailChangeLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberIntroChangeLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberLoginLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberNameChangeLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Logs.MemberSummaryChangeLog", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Member", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.MemberGrade", "MemberGrade")
+                        .WithMany()
+                        .HasForeignKey("MemberGradeID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.Navigation("MemberGrade");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.MemberApprove", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithOne("MemberApprove")
+                        .HasForeignKey("Domain.Entities.Members.MemberApprove", "MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.MemberStats", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithOne("MemberStats")
+                        .HasForeignKey("Domain.Entities.Members.MemberStats", "MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Banner.BannerItem", b =>
+                {
+                    b.HasOne("Domain.Entities.Page.Banner.BannerPosition", "BannerPosition")
+                        .WithMany("BannerItems")
+                        .HasForeignKey("PositionID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("BannerPosition");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Faq.FaqItem", b =>
+                {
+                    b.HasOne("Domain.Entities.Page.Faq.FaqCategory", "FaqCategory")
+                        .WithMany("FaqItems")
+                        .HasForeignKey("CategoryID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("FaqCategory");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.Wallet", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithOne("Wallet")
+                        .HasForeignKey("Domain.Entities.Wallets.Wallet", "MemberID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Member");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.WalletBalance", b =>
+                {
+                    b.HasOne("Domain.Entities.Wallets.Wallet", null)
+                        .WithMany("Balances")
+                        .HasForeignKey("WalletKey")
+                        .HasPrincipalKey("WalletKey")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.OwnsOne("Domain.Entities.Common.ValueObject.Money", "Amount", b1 =>
+                        {
+                            b1.Property<int>("WalletBalanceID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("Currency")
+                                .IsRequired()
+                                .HasMaxLength(10)
+                                .HasColumnType("nvarchar(10)")
+                                .HasColumnName("Currency");
+
+                            b1.Property<decimal>("Value")
+                                .HasPrecision(18)
+                                .HasColumnType("decimal(18,0)")
+                                .HasColumnName("Amount");
+
+                            b1.HasKey("WalletBalanceID");
+
+                            b1.ToTable("WalletBalance");
+
+                            b1.WithOwner()
+                                .HasForeignKey("WalletBalanceID");
+                        });
+
+                    b.Navigation("Amount")
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.WalletTransaction", b =>
+                {
+                    b.HasOne("Domain.Entities.Wallets.Wallet", "Wallet")
+                        .WithMany("Transactions")
+                        .HasForeignKey("WalletKey")
+                        .HasPrincipalKey("WalletKey")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.OwnsOne("Domain.Entities.Common.ValueObject.Money", "Amount", b1 =>
+                        {
+                            b1.Property<int>("WalletTransactionID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("Currency")
+                                .IsRequired()
+                                .HasMaxLength(10)
+                                .HasColumnType("nvarchar(10)")
+                                .HasColumnName("Currency");
+
+                            b1.Property<decimal>("Value")
+                                .HasPrecision(18)
+                                .HasColumnType("decimal(18,0)")
+                                .HasColumnName("Amount");
+
+                            b1.HasKey("WalletTransactionID");
+
+                            b1.ToTable("WalletTransaction");
+
+                            b1.WithOwner()
+                                .HasForeignKey("WalletTransactionID");
+                        });
+
+                    b.OwnsOne("Domain.Entities.Common.ValueObject.Money", "BalanceAfter", b1 =>
+                        {
+                            b1.Property<int>("WalletTransactionID")
+                                .HasColumnType("int");
+
+                            b1.Property<string>("Currency")
+                                .IsRequired()
+                                .HasMaxLength(10)
+                                .HasColumnType("nvarchar(10)")
+                                .HasColumnName("BalanceAfterCurrency");
+
+                            b1.Property<decimal>("Value")
+                                .HasPrecision(18)
+                                .HasColumnType("decimal(18,0)")
+                                .HasColumnName("BalanceAfter");
+
+                            b1.HasKey("WalletTransactionID");
+
+                            b1.ToTable("WalletTransaction");
+
+                            b1.WithOwner()
+                                .HasForeignKey("WalletTransactionID");
+                        });
+
+                    b.Navigation("Amount")
+                        .IsRequired();
+
+                    b.Navigation("BalanceAfter")
+                        .IsRequired();
+
+                    b.Navigation("Wallet");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.Board", b =>
+                {
+                    b.Navigation("BoardManager");
+
+                    b.Navigation("BoardMeta")
+                        .IsRequired();
+
+                    b.Navigation("BoardPrefix");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Boards.BoardGroup", b =>
+                {
+                    b.Navigation("Board");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Comments.Comment", b =>
+                {
+                    b.Navigation("Children");
+
+                    b.Navigation("CommentFile");
+
+                    b.Navigation("CommentFileDownLog");
+
+                    b.Navigation("CommentImage");
+
+                    b.Navigation("CommentLink");
+
+                    b.Navigation("CommentLinkClickLog");
+
+                    b.Navigation("CommentMedia");
+
+                    b.Navigation("CommentMention");
+
+                    b.Navigation("CommentReaction");
+
+                    b.Navigation("CommentReport");
+
+                    b.Navigation("CommentUpdateLog");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.Post", b =>
+                {
+                    b.Navigation("Comment");
+
+                    b.Navigation("PostBookmark");
+
+                    b.Navigation("PostFile");
+
+                    b.Navigation("PostFileDownLog");
+
+                    b.Navigation("PostImage");
+
+                    b.Navigation("PostLink");
+
+                    b.Navigation("PostLinkClickLog");
+
+                    b.Navigation("PostMedia");
+
+                    b.Navigation("PostReaction");
+
+                    b.Navigation("PostReport");
+
+                    b.Navigation("PostTag");
+
+                    b.Navigation("PostUpdateLog");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Forum.Posts.Tag", b =>
+                {
+                    b.Navigation("PostTag");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Members.Member", b =>
+                {
+                    b.Navigation("Channel");
+
+                    b.Navigation("MemberApprove")
+                        .IsRequired();
+
+                    b.Navigation("MemberStats")
+                        .IsRequired();
+
+                    b.Navigation("Wallet");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Banner.BannerPosition", b =>
+                {
+                    b.Navigation("BannerItems");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Faq.FaqCategory", b =>
+                {
+                    b.Navigation("FaqItems");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.Wallet", b =>
+                {
+                    b.Navigation("Balances");
+
+                    b.Navigation("Transactions");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 88 - 0
Infrastructure/Persistence/Migrations/20260212015759_a6.cs

@@ -0,0 +1,88 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Persistence.Migrations
+{
+    /// <inheritdoc />
+    public partial class a6 : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.CreateTable(
+                name: "AdminAccessLog",
+                columns: table => new
+                {
+                    ID = table.Column<long>(type: "bigint", nullable: false, comment: "PK")
+                        .Annotation("SqlServer:Identity", "1, 1"),
+                    UserID = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false, comment: "관리자 사용자 ID"),
+                    UserName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true, comment: "관리자 사용자 이름"),
+                    Method = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false, comment: "HTTP Method"),
+                    Path = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: false, comment: "요청 경로"),
+                    QueryString = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true, comment: "쿼리 스트링"),
+                    StatusCode = table.Column<int>(type: "int", nullable: false, comment: "응답 상태 코드"),
+                    ElapsedMs = table.Column<long>(type: "bigint", nullable: false, comment: "처리 시간 (밀리초)"),
+                    MenuName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true, comment: "메뉴 이름"),
+                    IpAddress = table.Column<string>(type: "nvarchar(45)", maxLength: 45, nullable: true, comment: "IP 주소"),
+                    UserAgent = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true, comment: "User Agent"),
+                    CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, comment: "생성 일시")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_AdminAccessLog", x => x.ID);
+                },
+                comment: "관리자 접근 기록");
+
+            migrationBuilder.CreateTable(
+                name: "AdminLoginLog",
+                columns: table => new
+                {
+                    ID = table.Column<int>(type: "int", nullable: false, comment: "PK")
+                        .Annotation("SqlServer:Identity", "1, 1"),
+                    Success = table.Column<bool>(type: "bit", nullable: false, comment: "로그인 성공 여부"),
+                    Account = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: false, comment: "로그인 시도 계정"),
+                    Reason = table.Column<string>(type: "nvarchar(225)", maxLength: 225, nullable: true, comment: "실패 사유"),
+                    IpAddress = table.Column<string>(type: "nvarchar(45)", maxLength: 45, nullable: true, comment: "IP 주소"),
+                    UserAgent = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true, comment: "User Agent"),
+                    CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, comment: "생성 일시")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_AdminLoginLog", x => x.ID);
+                },
+                comment: "관리자 로그인 기록");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_AdminAccessLog_CreatedAt",
+                table: "AdminAccessLog",
+                column: "CreatedAt");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_AdminAccessLog_UserID",
+                table: "AdminAccessLog",
+                column: "UserID");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_AdminLoginLog_Account",
+                table: "AdminLoginLog",
+                column: "Account");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_AdminLoginLog_CreatedAt",
+                table: "AdminLoginLog",
+                column: "CreatedAt");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropTable(
+                name: "AdminAccessLog");
+
+            migrationBuilder.DropTable(
+                name: "AdminLoginLog");
+        }
+    }
+}

+ 126 - 0
Infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs

@@ -50,6 +50,132 @@ namespace Infrastructure.Persistence.Migrations
                         });
                 });
 
+            modelBuilder.Entity("Domain.Entities.Director.AdminAccessLog", b =>
+                {
+                    b.Property<long>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("생성 일시");
+
+                    b.Property<long>("ElapsedMs")
+                        .HasColumnType("bigint")
+                        .HasComment("처리 시간 (밀리초)");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP 주소");
+
+                    b.Property<string>("MenuName")
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("메뉴 이름");
+
+                    b.Property<string>("Method")
+                        .IsRequired()
+                        .HasMaxLength(10)
+                        .HasColumnType("nvarchar(10)")
+                        .HasComment("HTTP Method");
+
+                    b.Property<string>("Path")
+                        .IsRequired()
+                        .HasMaxLength(2048)
+                        .HasColumnType("nvarchar(2048)")
+                        .HasComment("요청 경로");
+
+                    b.Property<string>("QueryString")
+                        .HasMaxLength(2048)
+                        .HasColumnType("nvarchar(2048)")
+                        .HasComment("쿼리 스트링");
+
+                    b.Property<int>("StatusCode")
+                        .HasColumnType("int")
+                        .HasComment("응답 상태 코드");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.Property<string>("UserID")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("nvarchar(64)")
+                        .HasComment("관리자 사용자 ID");
+
+                    b.Property<string>("UserName")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("관리자 사용자 이름");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.HasIndex("UserID");
+
+                    b.ToTable("AdminAccessLog", null, t =>
+                        {
+                            t.HasComment("관리자 접근 기록");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Director.AdminLoginLog", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("Account")
+                        .IsRequired()
+                        .HasMaxLength(120)
+                        .HasColumnType("nvarchar(120)")
+                        .HasComment("로그인 시도 계정");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("생성 일시");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(45)
+                        .HasColumnType("nvarchar(45)")
+                        .HasComment("IP 주소");
+
+                    b.Property<string>("Reason")
+                        .HasMaxLength(225)
+                        .HasColumnType("nvarchar(225)")
+                        .HasComment("실패 사유");
+
+                    b.Property<bool>("Success")
+                        .HasColumnType("bit")
+                        .HasComment("로그인 성공 여부");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("nvarchar(512)")
+                        .HasComment("User Agent");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Account");
+
+                    b.HasIndex("CreatedAt");
+
+                    b.ToTable("AdminLoginLog", null, t =>
+                        {
+                            t.HasComment("관리자 로그인 기록");
+                        });
+                });
+
             modelBuilder.Entity("Domain.Entities.EmailVerification.EmailVerifyNumber", b =>
                 {
                     b.Property<int>("ID")

+ 34 - 8
SharedKernel/Constants/Menus.cs

@@ -83,38 +83,64 @@ namespace SharedKernel.Constants
                         },
                         new Menu
                         {
-                            Id = 207,
+                            Id = 206,
                             Name = "회원 설정",
                             Path = "/Config/Register",
                             Roles = [ "Admin", "환경 - 회원 설정" ]
                         },
                         new Menu
                         {
-                            Id = 208,
+                            Id = 207,
                             Name = "알림 발송 확인",
                             Path = "/Config/Test/Email",
                             Roles = [ "Admin", "환경 - 알림 발송 확인" ]
                         },
                         new Menu
                         {
-                            Id = 209,
+                            Id = 208,
                             Name = "알림 발송 양식",
                             Path = "/Config/Template/Email",
                             Roles = [ "Admin", "환경 - 알림 발송 양식" ]
                         },
                         new Menu
                         {
-                            Id = 211,
+                            Id = 209,
                             Name = "API 설정",
                             Path = "/Config/External",
                             Roles = [ "Admin", "환경 - API 설정" ]
-                        },
+                        }
+                    }
+                },
+
+                new Menu
+                {
+                    Id = 250,
+                    Name = "관리자",
+                    Path = null,
+                    Icon = "<i class=\"bi bi-person-fill-gear\"></i>",
+                    Roles = [ "Admin", "관리자" ],
+                    Children = new List<Menu>
+                    {
                         new Menu
                         {
-                            Id = 213,
-                            Name = "관리자",
+                            Id = 251,
+                            Name = "회원 관리",
                             Path = "/Director/User",
-                            Roles = [ "Admin", "환경 - 관리자" ]
+                            Roles = [ "Admin", "관리자 회원" ]
+                        },
+                        new Menu
+                        {
+                            Id = 252,
+                            Name = "로그인 내역",
+                            Path = "/Director/LoginLog",
+                            Roles = [ "Admin", "관리자 - 로그인 내역" ]
+                        },
+                        new Menu
+                        {
+                            Id = 253,
+                            Name = "접근 기록",
+                            Path = "/Director/AccessLog",
+                            Roles = [ "Admin", "관리자 - 처리 기록" ]
                         }
                     }
                 },