瀏覽代碼

no message

KIM-JINO5 3 月之前
父節點
當前提交
b4d843ff8a
共有 98 個文件被更改,包括 14223 次插入1182 次删除
  1. 4 1
      .claude/settings.local.json
  2. 2 0
      Admin/Admin.csproj
  3. 5 2
      Admin/Pages/Crypto/Category.cshtml
  4. 18 13
      Admin/Pages/Crypto/Curation.cshtml
  5. 3 3
      Admin/Pages/Crypto/Curation.cshtml.cs
  6. 14 0
      Admin/Pages/Crypto/List/Edit.cshtml
  7. 2 0
      Admin/Pages/Crypto/List/Edit.cshtml.cs
  8. 38 2
      Admin/Pages/Crypto/List/Index.cshtml
  9. 33 1
      Admin/Pages/Crypto/List/Index.cshtml.cs
  10. 2 2
      Admin/Pages/Crypto/TickerConfig.cshtml
  11. 1 1
      Admin/Pages/Crypto/TickerConfig.cshtml.cs
  12. 6 1
      Admin/Program.cs
  13. 1 0
      Admin/using.cs
  14. 二進制
      Admin/wwwroot/uploads/crypto/2/abb2d1bbdae14b0bb07bbc2dab91b5bd.jpeg
  15. 10 10
      Application/Abstractions/Cache/CacheKeys.cs
  16. 97 0
      Application/Abstractions/Crypto/CryptoHubData.cs
  17. 10 0
      Application/Abstractions/Crypto/ICryptoHubClient.cs
  18. 10 0
      Application/Abstractions/Crypto/ICryptoHubService.cs
  19. 153 67
      Application/Abstractions/Crypto/IUpbitClient.cs
  20. 1 0
      Application/Abstractions/Data/IAppDbContext.cs
  21. 0 4
      Application/Application.csproj
  22. 2 0
      Application/Features/Admin/Crypto/List/Get/Handler.cs
  23. 1 0
      Application/Features/Admin/Crypto/List/Get/Response.cs
  24. 31 4
      Application/Features/Admin/Crypto/List/Search/Handler.cs
  25. 2 1
      Application/Features/Admin/Crypto/List/Search/Query.cs
  26. 11 1
      Application/Features/Admin/Crypto/List/Search/Response.cs
  27. 5 0
      Application/Features/Admin/Crypto/List/Sync/Command.cs
  28. 87 0
      Application/Features/Admin/Crypto/List/Sync/Handler.cs
  29. 3 0
      Application/Features/Admin/Crypto/List/Sync/Response.cs
  30. 9 5
      Application/Features/Api/Crypto/Candle/GetDays/Handler.cs
  31. 1 1
      Application/Features/Api/Crypto/Candle/GetDays/Query.cs
  32. 12 7
      Application/Features/Api/Crypto/Candle/GetDays/Response.cs
  33. 1 1
      Application/Features/Api/Crypto/Candle/GetLive/Handler.cs
  34. 1 1
      Application/Features/Api/Crypto/Candle/GetLive/Query.cs
  35. 10 7
      Application/Features/Api/Crypto/Candle/GetLive/Response.cs
  36. 7 5
      Application/Features/Api/Crypto/Candle/GetMinutes/Handler.cs
  37. 1 1
      Application/Features/Api/Crypto/Candle/GetMinutes/Query.cs
  38. 10 7
      Application/Features/Api/Crypto/Candle/GetMinutes/Response.cs
  39. 7 5
      Application/Features/Api/Crypto/Candle/GetMonths/Handler.cs
  40. 1 1
      Application/Features/Api/Crypto/Candle/GetMonths/Query.cs
  41. 10 7
      Application/Features/Api/Crypto/Candle/GetMonths/Response.cs
  42. 7 6
      Application/Features/Api/Crypto/Candle/GetSeconds/Handler.cs
  43. 1 1
      Application/Features/Api/Crypto/Candle/GetSeconds/Query.cs
  44. 9 7
      Application/Features/Api/Crypto/Candle/GetSeconds/Response.cs
  45. 7 5
      Application/Features/Api/Crypto/Candle/GetWeeks/Handler.cs
  46. 1 1
      Application/Features/Api/Crypto/Candle/GetWeeks/Query.cs
  47. 10 7
      Application/Features/Api/Crypto/Candle/GetWeeks/Response.cs
  48. 7 5
      Application/Features/Api/Crypto/Candle/GetYears/Handler.cs
  49. 1 1
      Application/Features/Api/Crypto/Candle/GetYears/Query.cs
  50. 10 7
      Application/Features/Api/Crypto/Candle/GetYears/Response.cs
  51. 12 5
      Application/Features/Api/Crypto/Market/GetAll/Handler.cs
  52. 16 3
      Application/Features/Api/Crypto/Market/GetAll/Response.cs
  53. 5 5
      Application/Features/Api/Crypto/Orderbook/Get/Handler.cs
  54. 1 1
      Application/Features/Api/Crypto/Orderbook/Get/Query.cs
  55. 2 1
      Application/Features/Api/Crypto/Orderbook/Get/Response.cs
  56. 2 2
      Application/Features/Api/Crypto/Orderbook/GetLive/Handler.cs
  57. 1 1
      Application/Features/Api/Crypto/Orderbook/GetLive/Query.cs
  58. 3 1
      Application/Features/Api/Crypto/Orderbook/GetLive/Response.cs
  59. 27 29
      Application/Features/Api/Crypto/Ticker/GetAll/Handler.cs
  60. 1 1
      Application/Features/Api/Crypto/Ticker/GetAll/Query.cs
  61. 11 0
      Application/Features/Api/Crypto/Ticker/GetAll/Response.cs
  62. 15 8
      Application/Features/Api/Crypto/Ticker/GetDetail/Handler.cs
  63. 1 1
      Application/Features/Api/Crypto/Ticker/GetDetail/Query.cs
  64. 12 4
      Application/Features/Api/Crypto/Ticker/GetDetail/Response.cs
  65. 1 1
      Application/Features/Api/Crypto/Trade/GetLive/Handler.cs
  66. 1 1
      Application/Features/Api/Crypto/Trade/GetLive/Query.cs
  67. 14 2
      Application/Features/Api/Crypto/Trade/GetLive/Response.cs
  68. 4 3
      Application/Features/Api/Crypto/Trade/GetRecent/Handler.cs
  69. 1 1
      Application/Features/Api/Crypto/Trade/GetRecent/Query.cs
  70. 2 0
      Application/Features/Api/Crypto/Trade/GetRecent/Response.cs
  71. 5 0
      Directory.Build.props
  72. 30 0
      Domain/Entities/Crypto/Coin.cs
  73. 18 0
      Domain/Entities/Crypto/CoinMarket.cs
  74. 123 62
      Infrastructure/Crypto/UpbitRestClient.cs
  75. 232 57
      Infrastructure/Crypto/UpbitWebSocketService.cs
  76. 1 1
      Infrastructure/DependencyInjection.cs
  77. 0 4
      Infrastructure/Infrastructure.csproj
  78. 1 0
      Infrastructure/Persistence/AppDbContext.cs
  79. 1 0
      Infrastructure/Persistence/Configurations/Crypto/CoinConfiguration.cs
  80. 20 0
      Infrastructure/Persistence/Configurations/Crypto/CoinMarketConfiguration.cs
  81. 6140 0
      Infrastructure/Persistence/Migrations/20260219090105_AddCoinMarket.Designer.cs
  82. 31 0
      Infrastructure/Persistence/Migrations/20260219090105_AddCoinMarket.cs
  83. 6177 0
      Infrastructure/Persistence/Migrations/20260219091041_AddCoinMarketTable.Designer.cs
  84. 66 0
      Infrastructure/Persistence/Migrations/20260219091041_AddCoinMarketTable.cs
  85. 45 0
      Infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs
  86. 3 1
      SharedKernel/SharedKernel.csproj
  87. 31 35
      Web.Api/Endpoints/Crypto/CryptoCandles.cs
  88. 3 3
      Web.Api/Endpoints/Crypto/CryptoMarkets.cs
  89. 9 6
      Web.Api/Endpoints/Crypto/CryptoOrderbook.cs
  90. 15 9
      Web.Api/Endpoints/Crypto/CryptoTickers.cs
  91. 11 10
      Web.Api/Endpoints/Crypto/CryptoTrades.cs
  92. 53 0
      Web.Api/Hubs/CryptoHub.cs
  93. 42 0
      Web.Api/Hubs/CryptoHubService.cs
  94. 7 0
      Web.Api/Program.cs
  95. 0 390
      Web.Api/bitforum-crypto-api.postman_collection.json
  96. 0 333
      Web.Api/bitforum-forum-api.postman_collection.json
  97. 347 0
      bitforum-crypto.postman_collection.json
  98. 5 0
      global.json

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

@@ -23,7 +23,10 @@
       "Bash(taskkill:*)",
       "WebFetch(domain:developercommunity.visualstudio.com)",
       "Bash(if exist .vs rmdir /s /q .vs)",
-      "Bash(grep:*)"
+      "Bash(grep:*)",
+      "Bash(tasklist:*)",
+      "Bash(Stop-Process -Id 7452 -Force)",
+      "WebFetch(domain:global-docs.upbit.com)"
     ]
   },
   "model": "opusplan",

+ 2 - 0
Admin/Admin.csproj

@@ -4,6 +4,7 @@
 		<TargetFramework>net10.0</TargetFramework>
 		<Nullable>enable</Nullable>
 		<ImplicitUsings>enable</ImplicitUsings>
+		<HotReloadAutoRestart>false</HotReloadAutoRestart>
 	</PropertyGroup>
 
 	<ItemGroup>
@@ -26,6 +27,7 @@
 			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 			<PrivateAssets>all</PrivateAssets>
 		</PackageReference>
+		<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.2" />
 		<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
 		<PackageReference Include="System.Management" Version="10.0.2" />
 	</ItemGroup>

+ 5 - 2
Admin/Pages/Crypto/Category.cshtml

@@ -25,8 +25,11 @@
 
         <table class="table table-striped table-bordered table-hover mt-3">
             <caption>
-                코인이 등록된 카테고리는 삭제할 수 없습니다.<br />
-                카테고리를 삭제하려면 해당 코인의 카테고리를 먼저 변경해 주세요.
+                <ul>
+                    <li>코인이 등록된 카테고리는 삭제할 수 없습니다.</li>
+                    <li>카테고리를 삭제하려면 해당 코인의 카테고리를 먼저 변경해 주세요.</li>
+                    <li>카테고리 순서는 숫자가 낮을수록 먼저 노출됩니다. (음수 허용)</li>
+                </ul>
             </caption>
             <colgroup>
                 <col style="width: 5%;" />

+ 18 - 13
Admin/Pages/Crypto/Curation.cshtml

@@ -24,27 +24,30 @@
 
         <table class="table table-striped table-bordered table-hover mt-3">
             <caption>
-                메인 페이지에 노출할 코인을 선택하고 순서를 지정하세요.<br />
-                Featured 체크된 코인만 메인 페이지에 노출됩니다.
+                <ul>
+                    <li>코인 순서는 숫자가 낮을수록 먼저 노출됩니다. (음수 허용)</li>
+                    <li>선별된 코인은 메인 페이지에 노출됩니다.</li>
+                </ul>
             </caption>
             <colgroup>
                 <col style="width: 5%;" />
-                <col style="width: 8%;" />
+                <col style="width: 8%;max-width: 128px;" />
                 <col style="width: 10%;" />
                 <col />
+                <col />
                 <col style="width: 10%;" />
                 <col style="width: 10%;" />
                 <col style="width: 10%;" />
             </colgroup>
             <thead>
-                <tr class="text-center">
+                <tr>
                     <th>No</th>
                     <th>로고</th>
                     <th>심볼</th>
                     <th>한글명</th>
                     <th>영문명</th>
                     <th>사용</th>
-                    <th>Featured</th>
+                    <th>선별</th>
                     <th>순서</th>
                 </tr>
             </thead>
@@ -62,23 +65,25 @@
                         var index = row.Index;
 
                         <tr class="@(row.IsFeatured ? "table-warning" : "")">
-                            <td class="text-center">
+                            <td>
                                 @row.Num
                                 <input type="hidden" name="request[@index].CoinID" form="fAdminWrite" value="@row.ID" />
                             </td>
-                            <td class="text-center">
+                            <td>
                                 @if (!string.IsNullOrEmpty(row.LogoImage))
                                 {
-                                    <img src="@row.LogoImage" alt="@row.Symbol" style="width: 24px; height: 24px;" />
+                                    <img src="@row.LogoImage" alt="@row.Symbol" style="width:64px;height:64px;object-fit:contain;" />
+                                } 
+                                else
+                                {
+                                    <text>-</text>    
                                 }
                             </td>
-                            <td class="text-center fw-bold">@row.Symbol</td>
+                            <td>@row.Symbol</td>
                             <td>@row.KorName</td>
                             <td>@row.EngName</td>
-                            <td class="text-center">
-                                @(row.IsActive ? "Y" : "N")
-                            </td>
-                            <td class="text-center">
+                            <td>@row.IsActive</td>
+                            <td>
                                 <div class="form-check d-flex justify-content-center">
                                     <input class="form-check-input" type="checkbox" id="request_@(index)_IsFeatured" name="request[@index].IsFeatured" checked="@row.IsFeatured" form="fAdminWrite" value="true" />
                                 </div>

+ 3 - 3
Admin/Pages/Crypto/Curation.cshtml.cs

@@ -18,7 +18,7 @@ public class CurationModel(IMediator mediator) : PageModel
         string? LogoImage,
         bool IsFeatured,
         short DisplayOrder,
-        bool IsActive
+        string IsActive
     )> List { get; set; } = [];
 
     [BindProperty(Name = "request")]
@@ -46,7 +46,7 @@ public class CurationModel(IMediator mediator) : PageModel
             c.LogoImage,
             c.IsFeatured,
             c.DisplayOrder,
-            c.IsActive
+            c.IsActive ? "Y" : "N"
         ))];
     }
 
@@ -69,7 +69,7 @@ public class CurationModel(IMediator mediator) : PageModel
 
             await mediator.Send(cmd, ct);
 
-            TempData["SuccessMessage"] = "저장 완료";
+            TempData["SuccessMessage"] = "저장되었습니다.";
         }
         catch (Exception e)
         {

+ 14 - 0
Admin/Pages/Crypto/List/Edit.cshtml

@@ -33,6 +33,20 @@
             </div>
         </div>
 
+        <div class="row mb-2">
+            <label class="col-sm-2 col-form-label">거래쌍</label>
+            <div class="col-sm-10 align-self-center">
+                @if (Model.Markets.Count > 0)
+                {
+                    <span>@string.Join(", ", Model.Markets)</span>
+                }
+                else
+                {
+                    <span class="text-muted">Upbit 동기화 후 자동 등록됩니다</span>
+                }
+            </div>
+        </div>
+
         <div class="row mb-2">
             <label asp-for="Input.KorName" class="col-sm-2 col-form-label">
                 <span class="text-danger">*</span> 한글명

+ 2 - 0
Admin/Pages/Crypto/List/Edit.cshtml.cs

@@ -12,6 +12,7 @@ namespace Admin.Pages.Crypto.List
     {
         public int CoinID { get; private set; }
         public string? CurrentLogoImage { get; private set; }
+        public IReadOnlyList<string> Markets { get; private set; } = [];
         public List<SelectListItem> Categories { get; set; } = [];
 
         [BindProperty]
@@ -74,6 +75,7 @@ namespace Admin.Pages.Crypto.List
 
                 CoinID = coin.ID;
                 CurrentLogoImage = coin.LogoImage;
+                Markets = coin.Markets;
 
                 var categories = await mediator.Send(new GetCryptoCategories.Query(), ct);
                 Categories = [..categories.List.Select(c => new SelectListItem(c.Name, c.ID.ToString()))];

+ 38 - 2
Admin/Pages/Crypto/List/Index.cshtml

@@ -64,11 +64,41 @@
             </select>
         </div>
         <div class="col-auto">
+            <button type="submit" class="btn btn-warning" form="fSyncForm" onclick="return confirm('Upbit 마켓 데이터를 동기화하시겠습니까?')">Upbit 동기화</button>
             <button type="button" id="btnListDelete" class="btn btn-danger" form="fAdminList" disabled>삭제</button>
             <a class="btn btn-success" asp-page="/Crypto/List/Write">추가</a>
         </div>
     </div>
 
+    @{
+        var tabQuery = new Dictionary<string, string?>(
+            Request.Query
+                .Where(q => !q.Key.Equals("tab", StringComparison.OrdinalIgnoreCase) && !q.Key.Equals("pageNum", StringComparison.OrdinalIgnoreCase))
+                .Select(q => new KeyValuePair<string, string?>(q.Key, q.Value.ToString()))
+        );
+
+        string TabHref(ushort tab)
+        {
+            tabQuery["tab"] = tab.ToString();
+            return $"/Crypto/List?{string.Join("&", tabQuery.Select(kv => $"{kv.Key}={Uri.EscapeDataString(kv.Value ?? "")}"))}";
+        }
+    }
+
+    <ul class="nav nav-tabs mt-2">
+        <li class="nav-item">
+            <a class="nav-link @(Model.Query.Tab == 0 ? "active" : null)" href="@TabHref(0)">전체(@Model.AllCount.ToString("N0"))</a>
+        </li>
+        <li class="nav-item">
+            <a class="nav-link @(Model.Query.Tab == 1 ? "active" : null)" href="@TabHref(1)">KRW(@Model.KrwCount.ToString("N0"))</a>
+        </li>
+        <li class="nav-item">
+            <a class="nav-link @(Model.Query.Tab == 2 ? "active" : null)" href="@TabHref(2)">BTC(@Model.BtcCount.ToString("N0"))</a>
+        </li>
+        <li class="nav-item">
+            <a class="nav-link @(Model.Query.Tab == 3 ? "active" : null)" href="@TabHref(3)">USDT(@Model.UsdtCount.ToString("N0"))</a>
+        </li>
+    </ul>
+
     <div class="table-responsive">
         <table class="table table-striped table-bordered table-hover mt-3">
             <colgroup>
@@ -93,7 +123,7 @@
                         </div>
                     </th>
                     <th>로고</th>
-                    <th>심볼</th>
+                    <th>거래쌍</th>
                     <th>한글명</th>
                     <th>영문명</th>
                     <th>카테고리</th>
@@ -133,7 +163,7 @@
                                     <span class="text-muted">-</span>
                                 }
                             </td>
-                            <td class="fw-bold">@row.Symbol</td>
+                            <td class="fw-bold">@row.Market</td>
                             <td>@row.KorName</td>
                             <td>@row.EngName</td>
                             <td>
@@ -170,6 +200,7 @@
 <!-- 검색용 폼 -->
 <form id="fAdminSearch" method="get" accept-charset="utf-8">
     <input type="hidden" name="pageNum" value="@Model.Query.PageNum" />
+    <input type="hidden" name="tab" value="@Model.Query.Tab" />
 </form>
 
 <!-- 목록용 폼 -->
@@ -181,6 +212,11 @@
     <input type="hidden" name="keyword" value="@Model.Query.Keyword" />
 </form>
 
+<!-- 동기화 폼 -->
+<form id="fSyncForm" method="post" accept-charset="utf-8" asp-page-handler="Sync">
+    @Html.AntiForgeryToken()
+</form>
+
 @section Scripts {
     <script>
         let searchForm = document.getElementById("fAdminSearch");

+ 33 - 1
Admin/Pages/Crypto/List/Index.cshtml.cs

@@ -32,6 +32,9 @@ namespace Admin.Pages.Crypto.List
             [DisplayName("검색어")]
             public string? Keyword { get; set; }
 
+            [DisplayName("거래쌍 구분")]
+            public ushort Tab { get; set; } = 0;
+
             public bool? IsActive { get; set; }
             public bool? IsWarning { get; set; }
             public bool? IsNew { get; set; }
@@ -43,6 +46,7 @@ namespace Admin.Pages.Crypto.List
         public List<(
             int Num,
             int ID,
+            string Market,
             string Symbol,
             string KorName,
             string EngName,
@@ -57,6 +61,11 @@ namespace Admin.Pages.Crypto.List
             string EditURL
         )> List { get; set; } = [];
 
+        public int AllCount { get; set; }
+        public int KrwCount { get; set; }
+        public int BtcCount { get; set; }
+        public int UsdtCount { get; set; }
+
         public Pagination? Pagination { get; set; }
 
         public async Task OnGetAsync(CancellationToken ct)
@@ -77,13 +86,20 @@ namespace Admin.Pages.Crypto.List
                 Query.IsNew,
                 Query.IsDelisted,
                 Query.PageNum,
-                Query.PerPage
+                Query.PerPage,
+                Query.Tab
             ), ct);
 
             Total = result.Total;
+            AllCount = result.AllCount;
+            KrwCount = result.KrwCount;
+            BtcCount = result.BtcCount;
+            UsdtCount = result.UsdtCount;
+
             List = [..result.List.Select(c => (
                 c.Num,
                 c.ID,
+                string.Join(", ", c.CoinMarkets.Select(x => x.Market)),
                 c.Symbol,
                 c.KorName,
                 c.EngName,
@@ -116,5 +132,21 @@ namespace Admin.Pages.Crypto.List
 
             return RedirectToPage("/Crypto/List/Index", Query);
         }
+
+        public async Task<IActionResult> OnPostSyncAsync(CancellationToken ct)
+        {
+            try
+            {
+                var result = await mediator.Send(new SyncCoins.Command(), ct);
+
+                TempData["SuccessMessage"] = $"동기화 완료: 신규 {result.Created}개, 갱신 {result.Updated}개, 상폐 {result.Delisted}개";
+            }
+            catch (Exception e)
+            {
+                TempData["ErrorMessages"] = e.Message;
+            }
+
+            return RedirectToPage("/Crypto/List/Index", Query);
+        }
     }
 }

+ 2 - 2
Admin/Pages/Crypto/TickerConfig.cshtml

@@ -25,7 +25,7 @@
         <div class="row mb-3">
             <label asp-for="Input.SurgeThreshold" class="col-sm-3 col-form-label">급등 임계값 (%)</label>
             <div class="col-sm-9">
-                <input asp-for="Input.SurgeThreshold" type="number" class="form-control" step="0.1" min="0.1" max="100" />
+                <input asp-for="Input.SurgeThreshold" type="number" class="form-control" step="any" min="0.1" max="100" />
                 <div class="form-text text-muted">24시간 기준 변동률이 이 값 이상이면 급등으로 표시합니다. 기본값: 5.0%</div>
                 <span asp-validation-for="Input.SurgeThreshold" class="text-danger"></span>
             </div>
@@ -34,7 +34,7 @@
         <div class="row mb-3">
             <label asp-for="Input.PlungeThreshold" class="col-sm-3 col-form-label">급락 임계값 (%)</label>
             <div class="col-sm-9">
-                <input asp-for="Input.PlungeThreshold" type="number" class="form-control" step="0.1" min="-100" max="-0.1" />
+                <input asp-for="Input.PlungeThreshold" type="number" class="form-control" step="any" min="-100" max="-0.1" />
                 <div class="form-text text-muted">24시간 기준 변동률이 이 값 이하이면 급락으로 표시합니다. 기본값: -5.0%</div>
                 <span asp-validation-for="Input.PlungeThreshold" class="text-danger"></span>
             </div>

+ 1 - 1
Admin/Pages/Crypto/TickerConfig.cshtml.cs

@@ -59,7 +59,7 @@ public class TickerConfigModel(IMediator mediator) : PageModel
                 Input.MainPageCoinCount
             ), ct);
 
-            TempData["SuccessMessage"] = "저장 완료";
+            TempData["SuccessMessage"] = "저장되었습니다.";
         }
         catch (Exception e)
         {

+ 6 - 1
Admin/Program.cs

@@ -15,12 +15,17 @@ Console.WriteLine($"ENV={builder.Environment.EnvironmentName}");
 Console.WriteLine($"현재 시간: {DateTime.Now} / {TimeZoneInfo.Local.Id}");
 
 // Add services to the container.
-builder.Services.AddRazorPages(options =>
+var mvcBuilder = builder.Services.AddRazorPages(options =>
 {
     options.Conventions.AuthorizeFolder("/");
     options.Conventions.AllowAnonymousToAreaFolder("Identity", "/Account");
 });
 
+if (builder.Environment.IsDevelopment())
+{
+    mvcBuilder.AddRazorRuntimeCompilation();
+}
+
 // 프로그램 설정 값 배치
 builder.Services.Configure<AppSettings>(builder.Configuration);
 

+ 1 - 0
Admin/using.cs

@@ -205,6 +205,7 @@ global using GetCoin = Application.Features.Admin.Crypto.List.Get;
 global using CreateCoin = Application.Features.Admin.Crypto.List.Create;
 global using UpdateCoin = Application.Features.Admin.Crypto.List.Update;
 global using DeleteCoin = Application.Features.Admin.Crypto.List.Delete;
+global using SyncCoins = Application.Features.Admin.Crypto.List.Sync;
 
 // 코인 게시판 연결
 global using GetCoinBoards = Application.Features.Admin.Crypto.Board.GetBoards;

二進制
Admin/wwwroot/uploads/crypto/2/abb2d1bbdae14b0bb07bbc2dab91b5bd.jpeg


+ 10 - 10
Application/Abstractions/Cache/CacheKeys.cs

@@ -14,19 +14,19 @@ public static class CacheKeys
     public const string CryptoCategoryActive = "crypto:category:active";
     public static string CoinBySymbol(string symbol) => $"crypto:coin:{symbol.ToLower()}";
 
-    // Crypto Ticker
-    public const string CryptoTickers = "crypto:tickers";
-    public static string CryptoTicker(string symbol) => $"crypto:ticker:{symbol.ToLower()}";
-    public static string CryptoCandle(string symbol, string type) => $"crypto:candle:{symbol.ToLower()}:{type}";
+    // Crypto Ticker (market = "KRW-BTC" 형식)
+    public static string CryptoTickers(string quote) => $"crypto:tickers:{quote.ToLower()}";
+    public static string CryptoTicker(string market) => $"crypto:ticker:{market.ToLower()}";
+    public static string CryptoCandle(string market, string type) => $"crypto:candle:{market.ToLower()}:{type}";
 
     // Crypto REST (초기 로딩용)
     public const string CryptoMarkets = "crypto:markets";
-    public static string CryptoTickerDetail(string symbol) => $"crypto:ticker:detail:{symbol.ToLower()}";
-    public static string CryptoTrades(string symbol) => $"crypto:trades:{symbol.ToLower()}";
-    public static string CryptoOrderbook(string symbol) => $"crypto:orderbook:{symbol.ToLower()}";
+    public static string CryptoTickerDetail(string market) => $"crypto:ticker:detail:{market.ToLower()}";
+    public static string CryptoTrades(string market) => $"crypto:trades:{market.ToLower()}";
+    public static string CryptoOrderbook(string market) => $"crypto:orderbook:{market.ToLower()}";
 
     // Crypto WebSocket Live (실시간용)
-    public static string CryptoCandleLive(string symbol, string interval) => $"crypto:candle:live:{symbol.ToLower()}:{interval}";
-    public static string CryptoTradeLive(string symbol) => $"crypto:trade:live:{symbol.ToLower()}";
-    public static string CryptoOrderbookLive(string symbol) => $"crypto:orderbook:live:{symbol.ToLower()}";
+    public static string CryptoCandleLive(string market, string interval) => $"crypto:candle:live:{market.ToLower()}:{interval}";
+    public static string CryptoTradeLive(string market) => $"crypto:trade:live:{market.ToLower()}";
+    public static string CryptoOrderbookLive(string market) => $"crypto:orderbook:live:{market.ToLower()}";
 }

+ 97 - 0
Application/Abstractions/Crypto/CryptoHubData.cs

@@ -0,0 +1,97 @@
+namespace Application.Abstractions.Crypto;
+
+public static class CryptoHubData
+{
+    // WebSocket Ticker 전체 필드
+    public sealed record TickerData(
+        string Market,
+        string Symbol,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        decimal PrevClosingPrice,
+        string Change,
+        decimal ChangePrice,
+        decimal SignedChangePrice,
+        decimal ChangeRate,
+        decimal SignedChangeRate,
+        decimal TradeVolume,
+        decimal AccTradeVolume,
+        decimal AccTradeVolume24h,
+        decimal AccTradePrice,
+        decimal AccTradePrice24h,
+        string TradeDate,
+        string TradeTime,
+        long TradeTimestamp,
+        string AskBid,
+        decimal AccAskVolume,
+        decimal AccBidVolume,
+        decimal Highest52WeekPrice,
+        string Highest52WeekDate,
+        decimal Lowest52WeekPrice,
+        string Lowest52WeekDate,
+        string MarketState,
+        string? DelistingDate,
+        string MarketWarning,
+        long Timestamp,
+        string StreamType
+    );
+
+    // WebSocket Trade 전체 필드
+    public sealed record TradeData(
+        string Market,
+        string Symbol,
+        decimal TradePrice,
+        decimal TradeVolume,
+        string AskBid,
+        decimal PrevClosingPrice,
+        string Change,
+        decimal ChangePrice,
+        string TradeDate,
+        string TradeTime,
+        long TradeTimestamp,
+        long SequentialId,
+        long Timestamp,
+        string StreamType,
+        decimal BestAskPrice,
+        decimal BestAskSize,
+        decimal BestBidPrice,
+        decimal BestBidSize
+    );
+
+    // WebSocket Orderbook 전체 필드
+    public sealed record OrderbookData(
+        string Market,
+        string Symbol,
+        decimal TotalAskSize,
+        decimal TotalBidSize,
+        IReadOnlyList<OrderbookUnitData> Units,
+        long Timestamp,
+        decimal Level,
+        string StreamType
+    );
+
+    public sealed record OrderbookUnitData(
+        decimal AskPrice,
+        decimal BidPrice,
+        decimal AskSize,
+        decimal BidSize
+    );
+
+    // WebSocket Candle 전체 필드
+    public sealed record CandleData(
+        string Market,
+        string Symbol,
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        decimal CandleAccTradeVolume,
+        decimal CandleAccTradePrice,
+        long Timestamp,
+        string StreamType
+    );
+}

+ 10 - 0
Application/Abstractions/Crypto/ICryptoHubClient.cs

@@ -0,0 +1,10 @@
+namespace Application.Abstractions.Crypto;
+
+public interface ICryptoHubClient
+{
+    Task ReceiveTicker(CryptoHubData.TickerData ticker);
+    Task ReceiveTickers(IReadOnlyList<CryptoHubData.TickerData> tickers);
+    Task ReceiveTrade(CryptoHubData.TradeData trade);
+    Task ReceiveOrderbook(CryptoHubData.OrderbookData orderbook);
+    Task ReceiveCandle(CryptoHubData.CandleData candle);
+}

+ 10 - 0
Application/Abstractions/Crypto/ICryptoHubService.cs

@@ -0,0 +1,10 @@
+namespace Application.Abstractions.Crypto;
+
+public interface ICryptoHubService
+{
+    Task SendTickerAsync(CryptoHubData.TickerData ticker, CancellationToken ct = default);
+    Task SendTickersAsync(IReadOnlyList<CryptoHubData.TickerData> tickers, CancellationToken ct = default);
+    Task SendTradeAsync(CryptoHubData.TradeData trade, CancellationToken ct = default);
+    Task SendOrderbookAsync(CryptoHubData.OrderbookData orderbook, CancellationToken ct = default);
+    Task SendCandleAsync(CryptoHubData.CandleData candle, CancellationToken ct = default);
+}

+ 153 - 67
Application/Abstractions/Crypto/IUpbitClient.cs

@@ -2,7 +2,7 @@ namespace Application.Abstractions.Crypto;
 
 public interface IUpbitClient
 {
-    // Candle
+    // Candle (캔들)
     Task<IReadOnlyList<UpbitCandle>> GetSecondCandlesAsync(string market, int count, CancellationToken ct = default);
     Task<IReadOnlyList<UpbitCandle>> GetMinuteCandlesAsync(string market, int unit, int count, CancellationToken ct = default);
     Task<IReadOnlyList<UpbitCandle>> GetDayCandlesAsync(string market, int count, CancellationToken ct = default);
@@ -10,98 +10,184 @@ public interface IUpbitClient
     Task<IReadOnlyList<UpbitCandle>> GetMonthCandlesAsync(string market, int count, CancellationToken ct = default);
     Task<IReadOnlyList<UpbitCandle>> GetYearCandlesAsync(string market, int count, CancellationToken ct = default);
 
-    // Market
+    // Market (마켓 목록)
     Task<IReadOnlyList<UpbitMarket>> GetMarketsAsync(CancellationToken ct = default);
 
-    // Ticker (상세)
+    // Ticker (현재가 상세)
     Task<IReadOnlyList<UpbitTickerDetail>> GetTickersAsync(string[] markets, CancellationToken ct = default);
 
-    // Trade (체결)
+    // Trade (최근 체결 내역)
     Task<IReadOnlyList<UpbitTrade>> GetTradesAsync(string market, int count, CancellationToken ct = default);
 
-    // Orderbook (호가)
+    // Orderbook (호가 정보)
     Task<IReadOnlyList<UpbitOrderbook>> GetOrderbookAsync(string[] markets, CancellationToken ct = default);
 }
 
-// WebSocket Ticker (SIMPLE 포맷, 기존)
+/// <summary>
+/// WebSocket 현재가(Ticker) — SIMPLE 포맷
+/// https://docs.upbit.com/kr/reference/websocket-ticker
+/// </summary>
 public sealed record UpbitTicker(
-    string Market,
-    decimal TradePrice,
-    string Change,
-    decimal SignedChangePrice,
-    decimal SignedChangeRate,
-    decimal AccTradePrice24h
+    string Market,                  // 페어(거래쌍)의 코드 (ty: ticker)
+    decimal OpeningPrice,           // 시가
+    decimal HighPrice,              // 고가
+    decimal LowPrice,               // 저가
+    decimal TradePrice,             // 현재가
+    decimal PrevClosingPrice,       // 전일 종가
+    string Change,                  // 전일 대비 (EVEN: 보합, RISE: 상승, FALL: 하락)
+    decimal ChangePrice,            // 전일 대비 값
+    decimal SignedChangePrice,      // 전일 대비 값 (부호 포함)
+    decimal ChangeRate,             // 전일 대비 등락율
+    decimal SignedChangeRate,       // 전일 대비 등락율 (부호 포함)
+    decimal TradeVolume,            // 가장 최근 거래량
+    decimal AccTradeVolume,         // 누적 거래량 (UTC 0시 기준)
+    decimal AccTradeVolume24h,      // 24시간 누적 거래량
+    decimal AccTradePrice,          // 누적 거래대금 (UTC 0시 기준)
+    decimal AccTradePrice24h,       // 24시간 누적 거래대금
+    string TradeDate,               // 최근 거래 일자 (UTC, yyyyMMdd)
+    string TradeTime,               // 최근 거래 시각 (UTC, HHmmss)
+    long TradeTimestamp,            // 체결 타임스탬프 (ms)
+    string AskBid,                  // 매수/매도 구분 (ASK: 매도, BID: 매수)
+    decimal AccAskVolume,           // 누적 매도량
+    decimal AccBidVolume,           // 누적 매수량
+    decimal Highest52WeekPrice,     // 52주 신고가
+    string Highest52WeekDate,       // 52주 신고가 달성일 (yyyy-MM-dd)
+    decimal Lowest52WeekPrice,      // 52주 신저가
+    string Lowest52WeekDate,        // 52주 신저가 달성일 (yyyy-MM-dd)
+    string MarketState,             // 거래 상태 (PREVIEW, ACTIVE, DELISTED)
+    string? DelistingDate,          // 상장폐지일
+    string MarketWarning,           // 유의 종목 여부 (NONE, CAUTION)
+    long Timestamp,                 // 타임스탬프 (ms)
+    string StreamType               // 스트림 타입 (SNAPSHOT, REALTIME)
 );
 
-// Candle
+/// <summary>
+/// REST/WebSocket 캔들 — 공통 + 타입별 선택 필드
+/// https://docs.upbit.com/kr/reference/list-candles-seconds
+/// https://docs.upbit.com/kr/reference/list-candles-minutes
+/// https://docs.upbit.com/kr/reference/list-candles-days
+/// https://docs.upbit.com/kr/reference/list-candles-weeks
+/// https://docs.upbit.com/kr/reference/list-candles-months
+/// https://docs.upbit.com/kr/reference/list-candles-years
+/// </summary>
 public sealed record UpbitCandle(
-    string Market,
-    DateTime CandleDateTimeUtc,
-    decimal OpeningPrice,
-    decimal HighPrice,
-    decimal LowPrice,
-    decimal TradePrice,
-    long Timestamp,
-    decimal CandleAccTradePrice,
-    decimal CandleAccTradeVolume,
-    string? FirstDayOfPeriod
+    string Market,                   // 페어(거래쌍)의 코드
+    string CandleDateTimeUtc,        // 캔들 기준 시각 (UTC)
+    string CandleDateTimeKst,        // 캔들 기준 시각 (KST)
+    decimal OpeningPrice,            // 시가
+    decimal HighPrice,               // 고가
+    decimal LowPrice,                // 저가
+    decimal TradePrice,              // 종가 (현재가)
+    long Timestamp,                  // 마지막 틱이 저장된 시각 (ms)
+    decimal CandleAccTradePrice,     // 누적 거래 금액
+    decimal CandleAccTradeVolume,    // 누적 거래량
+
+    // 분(Minutes) 캔들 전용
+    int? Unit,                       // 캔들 집계 시간 단위 (분: 1, 3, 5, 10, 15, 30, 60, 240)
+
+    // 일(Days) 캔들 전용
+    decimal? PrevClosingPrice,       // 전일 종가 (UTC 0시 기준)
+    decimal? ChangePrice,            // 전일 종가 대비 가격 변화
+    decimal? ChangeRate,             // 전일 종가 대비 가격 변화율
+    decimal? ConvertedTradePrice,    // 종가 환산 화폐 단위로 환산된 가격 (요청 시)
+
+    // 주(Weeks), 월(Months), 연(Years) 캔들 전용
+    string? FirstDayOfPeriod         // 캔들 집계 시작일자 (yyyy-MM-dd)
 );
 
-// 마켓 목록
+/// <summary>
+/// 마켓 목록
+/// https://docs.upbit.com/kr/reference/list-trading-pairs
+/// </summary>
 public sealed record UpbitMarket(
-    string Market,
-    string KorName,
-    string EngName,
-    string? MarketWarning
+    string Market,                   // 페어(거래쌍)의 코드
+    string KoreanName,               // 가상자산 한글명
+    string EnglishName,              // 가상자산 영문명
+    UpbitMarketEvent MarketEvent     // 종목 유의/경보 정보
+);
+
+/// <summary>마켓 이벤트 정보 (유의 종목)</summary>
+public sealed record UpbitMarketEvent(
+    bool Warning,                    // 유의 종목 여부
+    UpbitMarketCaution Caution       // 주의 종목 상세
+);
+
+/// <summary>마켓 주의 종목 상세</summary>
+public sealed record UpbitMarketCaution(
+    bool PriceFluctuations,               // 가격 급등락 경보
+    bool TradingVolumeSoaring,            // 거래량 급증 경보
+    bool DepositAmountSoaring,            // 입금량 급증 경보
+    bool GlobalPriceDifferences,          // 국내외 가격 차이 경보
+    bool ConcentrationOfSmallAccounts     // 소수 계정 집중 거래 경보
 );
 
-// Ticker 상세 (REST)
+/// <summary>
+/// REST 현재가(Ticker) 상세
+/// https://docs.upbit.com/kr/reference/list-tickers
+/// </summary>
 public sealed record UpbitTickerDetail(
-    string Market,
-    decimal TradePrice,
-    string Change,
-    decimal SignedChangePrice,
-    decimal SignedChangeRate,
-    decimal OpeningPrice,
-    decimal HighPrice,
-    decimal LowPrice,
-    decimal PrevClosingPrice,
-    decimal AccTradePrice,
-    decimal AccTradePrice24h,
-    decimal AccTradeVolume,
-    decimal AccTradeVolume24h,
-    decimal Highest52WeekPrice,
-    string Highest52WeekDate,
-    decimal Lowest52WeekPrice,
-    string Lowest52WeekDate,
-    long Timestamp
+    string Market,                   // 페어(거래쌍)의 코드
+    string TradeDate,                // 최근 거래 일자 (UTC, yyyyMMdd)
+    string TradeTime,                // 최근 거래 시각 (UTC, HHmmss)
+    string TradeDateKst,             // 최근 거래 일자 (KST, yyyyMMdd)
+    string TradeTimeKst,             // 최근 거래 시각 (KST, HHmmss)
+    long TradeTimestamp,             // 체결 타임스탬프 (ms)
+    decimal OpeningPrice,            // 시가
+    decimal HighPrice,               // 고가
+    decimal LowPrice,                // 저가
+    decimal TradePrice,              // 현재가
+    decimal PrevClosingPrice,        // 전일 종가
+    string Change,                   // 전일 대비 (EVEN, RISE, FALL)
+    decimal ChangePrice,             // 전일 대비 값
+    decimal ChangeRate,              // 전일 대비 등락율
+    decimal SignedChangePrice,       // 전일 대비 값 (부호 포함)
+    decimal SignedChangeRate,        // 전일 대비 등락율 (부호 포함)
+    decimal TradeVolume,             // 가장 최근 거래량
+    decimal AccTradePrice,           // 누적 거래대금 (UTC 0시 기준)
+    decimal AccTradePrice24h,        // 24시간 누적 거래대금
+    decimal AccTradeVolume,          // 누적 거래량 (UTC 0시 기준)
+    decimal AccTradeVolume24h,       // 24시간 누적 거래량
+    decimal Highest52WeekPrice,      // 52주 신고가
+    string Highest52WeekDate,        // 52주 신고가 달성일 (yyyy-MM-dd)
+    decimal Lowest52WeekPrice,       // 52주 신저가
+    string Lowest52WeekDate,         // 52주 신저가 달성일 (yyyy-MM-dd)
+    long Timestamp                   // 타임스탬프 (ms)
 );
 
-// 체결 내역
+/// <summary>
+/// REST 최근 체결 내역
+/// https://docs.upbit.com/kr/reference/recent-trades-history
+/// </summary>
 public sealed record UpbitTrade(
-    string Market,
-    string TradeDate,
-    string TradeTime,
-    long Timestamp,
-    decimal TradePrice,
-    decimal TradeVolume,
-    decimal PrevClosingPrice,
-    string AskBid,
-    long SequentialId
+    string Market,                   // 페어(거래쌍)의 코드
+    string TradeDateUtc,             // 체결 일자 (UTC)
+    string TradeTimeUtc,             // 체결 시각 (UTC)
+    long Timestamp,                  // 타임스탬프 (ms)
+    decimal TradePrice,              // 체결 가격
+    decimal TradeVolume,             // 체결량
+    decimal PrevClosingPrice,        // 전일 종가
+    decimal ChangePrice,             // 전일 대비 값
+    string AskBid,                   // 매수/매도 구분 (ASK: 매도, BID: 매수)
+    long SequentialId                // 체결 번호 (Unique)
 );
 
-// 호가
+/// <summary>
+/// REST 호가 정보
+/// https://docs.upbit.com/kr/reference/list-orderbooks
+/// </summary>
 public sealed record UpbitOrderbook(
-    string Market,
-    decimal TotalAskSize,
-    decimal TotalBidSize,
-    IReadOnlyList<UpbitOrderbookUnit> OrderbookUnits,
-    long Timestamp
+    string Market,                   // 페어(거래쌍)의 코드
+    decimal TotalAskSize,            // 호가 매도 총 잔량
+    decimal TotalBidSize,            // 호가 매수 총 잔량
+    IReadOnlyList<UpbitOrderbookUnit> OrderbookUnits, // 호가 리스트
+    long Timestamp,                  // 타임스탬프 (ms)
+    decimal Level                    // 호가 모아보기 단위
 );
 
+/// <summary>호가 단위</summary>
 public sealed record UpbitOrderbookUnit(
-    decimal AskPrice,
-    decimal BidPrice,
-    decimal AskSize,
-    decimal BidSize
+    decimal AskPrice,                // 매도호가
+    decimal BidPrice,                // 매수호가
+    decimal AskSize,                 // 매도 잔량
+    decimal BidSize                  // 매수 잔량
 );

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

@@ -26,6 +26,7 @@ namespace Application.Abstractions.Data
         DbSet<CoinCategory> CoinCategory { get; set; }
         DbSet<Coin> Coin { get; set; }
         DbSet<CoinCategoryMap> CoinCategoryMap { get; set; }
+        DbSet<CoinMarket> CoinMarket { get; set; }
 
         // 각종 설정 및 페이지
         DbSet<Config> Config { get; set;  }

+ 0 - 4
Application/Application.csproj

@@ -12,10 +12,6 @@
     <Folder Include="Features\ReferenceData\Dtos\" />
   </ItemGroup>
 
-  <ItemGroup>
-    <FrameworkReference Include="Microsoft.AspNetCore.App" />
-  </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="MediatR" Version="14.0.0" />
     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />

+ 2 - 0
Application/Features/Admin/Crypto/List/Get/Handler.cs

@@ -11,6 +11,7 @@ namespace Application.Features.Admin.Crypto.List.Get
             var coin = await db.Coin
                 .AsNoTracking()
                 .Include(c => c.CoinCategoryMap)
+                .Include(c => c.CoinMarket)
                 .FirstOrDefaultAsync(c => c.ID == request.ID, ct);
 
             if (coin is null)
@@ -21,6 +22,7 @@ namespace Application.Features.Admin.Crypto.List.Get
             return new Response(
                 coin.ID,
                 coin.Symbol,
+                [..coin.CoinMarket.Select(m => m.Market)],
                 coin.KorName,
                 coin.EngName,
                 coin.LogoImage,

+ 1 - 0
Application/Features/Admin/Crypto/List/Get/Response.cs

@@ -3,6 +3,7 @@ namespace Application.Features.Admin.Crypto.List.Get
     public sealed record Response(
         int ID,
         string Symbol,
+        IReadOnlyList<string> Markets,
         string KorName,
         string EngName,
         string? LogoImage,

+ 31 - 4
Application/Features/Admin/Crypto/List/Search/Handler.cs

@@ -8,9 +8,7 @@ namespace Application.Features.Admin.Crypto.List.Search
     {
         public async Task<Response> Handle(Query request, CancellationToken ct)
         {
-            var query = db.Coin.AsNoTracking()
-                .Include(c => c.CoinCategoryMap).ThenInclude(m => m.CoinCategory)
-                .AsQueryable();
+            var query = db.Coin.AsNoTracking().Include(c => c.CoinCategoryMap).ThenInclude(m => m.CoinCategory).AsQueryable();
 
             if (request.CategoryID.HasValue)
             {
@@ -43,6 +41,29 @@ namespace Application.Features.Admin.Crypto.List.Search
                 query = query.Where(c => c.IsDelisted == request.IsDelisted.Value);
             }
 
+            var baseQuery = query;
+
+            var allCount = await baseQuery.CountAsync(ct);
+            var krwCount = await baseQuery.Where(c => c.CoinMarket.Any(m => m.Market.StartsWith("KRW-"))).CountAsync(ct);
+            var btcCount = await baseQuery.Where(c => c.CoinMarket.Any(m => m.Market.StartsWith("BTC-"))).CountAsync(ct);
+            var usdtCount = await baseQuery.Where(c => c.CoinMarket.Any(m => m.Market.StartsWith("USDT-"))).CountAsync(ct);
+
+            if (request.Tab > 0)
+            {
+                var prefix = request.Tab switch
+                {
+                    1 => "KRW-",
+                    2 => "BTC-",
+                    3 => "USDT-",
+                    _ => ""
+                };
+
+                if (prefix.Length > 0)
+                {
+                    query = query.Where(c => c.CoinMarket.Any(m => m.Market.StartsWith(prefix)));
+                }
+            }
+
             var total = await query.CountAsync(ct);
 
             var list = await query
@@ -52,6 +73,7 @@ namespace Application.Features.Admin.Crypto.List.Search
                 .Select(c => new
                 {
                     c.ID,
+                    c.CoinMarket,
                     c.Symbol,
                     c.KorName,
                     c.EngName,
@@ -73,6 +95,7 @@ namespace Application.Features.Admin.Crypto.List.Search
                 [..list.Select((c, i) => new Response.Row(
                     Num: startNum - i,
                     c.ID,
+                    c.CoinMarket,
                     c.Symbol,
                     c.KorName,
                     c.EngName,
@@ -84,7 +107,11 @@ namespace Application.Features.Admin.Crypto.List.Search
                     c.IsDelisted,
                     c.UpdatedAt,
                     c.CreatedAt
-                ))]
+                ))],
+                allCount,
+                krwCount,
+                btcCount,
+                usdtCount
             );
         }
     }

+ 2 - 1
Application/Features/Admin/Crypto/List/Search/Query.cs

@@ -10,6 +10,7 @@ namespace Application.Features.Admin.Crypto.List.Search
         bool? IsNew,
         bool? IsDelisted,
         int PageNum,
-        ushort PerPage
+        ushort PerPage,
+        ushort Tab
     ) : IQuery<Response>;
 }

+ 11 - 1
Application/Features/Admin/Crypto/List/Search/Response.cs

@@ -1,10 +1,20 @@
+using Domain.Entities.Crypto;
+
 namespace Application.Features.Admin.Crypto.List.Search
 {
-    public sealed record Response(int Total, List<Response.Row> List)
+    public sealed record Response(
+        int Total,
+        List<Response.Row> List,
+        int AllCount,
+        int KrwCount,
+        int BtcCount,
+        int UsdtCount
+    )
     {
         public sealed record Row(
             int Num,
             int ID,
+            List<CoinMarket> CoinMarkets,
             string Symbol,
             string KorName,
             string EngName,

+ 5 - 0
Application/Features/Admin/Crypto/List/Sync/Command.cs

@@ -0,0 +1,5 @@
+using Application.Abstractions.Messaging;
+
+namespace Application.Features.Admin.Crypto.List.Sync;
+
+public sealed record Command() : ICommand<Response>;

+ 87 - 0
Application/Features/Admin/Crypto/List/Sync/Handler.cs

@@ -0,0 +1,87 @@
+using Application.Abstractions.Messaging;
+using Application.Abstractions.Data;
+using Application.Abstractions.Cache;
+using Application.Abstractions.Crypto;
+using Domain.Entities.Crypto;
+using Microsoft.EntityFrameworkCore;
+
+namespace Application.Features.Admin.Crypto.List.Sync;
+
+public sealed class Handler(IAppDbContext db, IUpbitClient upbit, ICacheService cache) : ICommandHandler<Command, Response>
+{
+    public async Task<Response> Handle(Command request, CancellationToken ct)
+    {
+        var allMarkets = await upbit.GetMarketsAsync(ct);
+
+        if (allMarkets.Count == 0)
+        {
+            throw new InvalidOperationException("Upbit API에서 마켓 데이터를 가져오지 못했습니다.");
+        }
+
+        var dbCoins = await db.Coin.Include(c => c.CoinMarket).ToListAsync(ct);
+        var dbCoinMap = dbCoins.ToDictionary(c => c.Symbol, c => c, StringComparer.OrdinalIgnoreCase);
+        var upbitSymbols = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+        int created = 0;
+        int updated = 0;
+        int delisted = 0;
+
+        // 심볼별 첫 마켓에서 Coin 생성/갱신
+        foreach (var market in allMarkets)
+        {
+            var symbol = market.Market.Split('-')[1];
+
+            if (!upbitSymbols.Add(symbol))
+            {
+                continue;
+            }
+
+            if (dbCoinMap.TryGetValue(symbol, out var existing))
+            {
+                existing.SyncUpdate(market.KoreanName, market.EnglishName, market.MarketEvent.Warning);
+                updated++;
+            }
+            else
+            {
+                var coin = Coin.SyncFromUpbit(symbol, market.KoreanName, market.EnglishName, market.MarketEvent.Warning);
+                db.Coin.Add(coin);
+                dbCoinMap[symbol] = coin;
+                created++;
+            }
+        }
+
+        // SaveChanges로 신규 코인 ID 확정
+        await db.SaveChangesAsync(ct);
+
+        // 거래쌍 동기화 (CoinMarket)
+        foreach (var market in allMarkets)
+        {
+            var symbol = market.Market.Split('-')[1];
+
+            if (!dbCoinMap.TryGetValue(symbol, out var coin))
+            {
+                continue;
+            }
+
+            if (!coin.CoinMarket.Any(m => m.Market.Equals(market.Market, StringComparison.OrdinalIgnoreCase)))
+            {
+                db.CoinMarket.Add(new CoinMarket { CoinID = coin.ID, Market = market.Market.ToUpper() });
+            }
+        }
+
+        // 상폐 처리
+        foreach (var coin in dbCoins)
+        {
+            if (!upbitSymbols.Contains(coin.Symbol) && !coin.IsDelisted)
+            {
+                coin.MarkDelisted();
+                delisted++;
+            }
+        }
+
+        await db.SaveChangesAsync(ct);
+        await cache.RemoveByPrefixAsync("crypto:", ct);
+
+        return new Response(created, updated, delisted);
+    }
+}

+ 3 - 0
Application/Features/Admin/Crypto/List/Sync/Response.cs

@@ -0,0 +1,3 @@
+namespace Application.Features.Admin.Crypto.List.Sync;
+
+public sealed record Response(int Created, int Updated, int Delisted);

+ 9 - 5
Application/Features/Api/Crypto/Candle/GetDays/Handler.cs

@@ -11,8 +11,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
         var count = Math.Clamp(request.Count, 1, 200);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoCandle(request.Symbol, "d");
+        var cacheKey = CacheKeys.CryptoCandle(request.Market, "d");
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.CandleItem>>(cacheKey, ct);
@@ -22,18 +21,23 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var candles = await upbit.GetDayCandlesAsync(market, count, ct);
+        var candles = await upbit.GetDayCandlesAsync(request.Market, count, ct);
 
         var items = candles
             .OrderBy(c => c.CandleDateTimeUtc)
             .Select(c => new Response.CandleItem(
-                new DateTimeOffset(c.CandleDateTimeUtc, TimeSpan.Zero).ToUnixTimeSeconds(),
+                c.CandleDateTimeUtc,
+                c.CandleDateTimeKst,
                 c.OpeningPrice,
                 c.HighPrice,
                 c.LowPrice,
                 c.TradePrice,
+                c.Timestamp,
+                c.CandleAccTradePrice,
                 c.CandleAccTradeVolume,
-                c.CandleAccTradePrice
+                c.PrevClosingPrice ?? 0,
+                c.ChangePrice ?? 0,
+                c.ChangeRate ?? 0
             ))
             .ToList();
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetDays/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetDays;
 
-public sealed record Query(string Symbol, int Count = 200) : IQuery<Response>;
+public sealed record Query(string Market, int Count = 200) : IQuery<Response>;

+ 12 - 7
Application/Features/Api/Crypto/Candle/GetDays/Response.cs

@@ -3,12 +3,17 @@ namespace Application.Features.Api.Crypto.Candle.GetDays;
 public sealed record Response(IReadOnlyList<Response.CandleItem> Candles)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        long Timestamp,
+        decimal CandleAccTradePrice,
+        decimal CandleAccTradeVolume,
+        decimal PrevClosingPrice,
+        decimal ChangePrice,
+        decimal ChangeRate
     );
 }

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetLive/Handler.cs

@@ -7,7 +7,7 @@ public sealed class Handler(ICacheService cache) : IQueryHandler<Query, Response
 {
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
-        var cacheKey = CacheKeys.CryptoCandleLive(request.Symbol, request.Interval);
+        var cacheKey = CacheKeys.CryptoCandleLive(request.Market, request.Interval);
 
         var cached = await cache.GetAsync<Response.CandleItem>(cacheKey, ct);
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetLive/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetLive;
 
-public sealed record Query(string Symbol, string Interval = "m1") : IQuery<Response>;
+public sealed record Query(string Market, string Interval = "m1") : IQuery<Response>;

+ 10 - 7
Application/Features/Api/Crypto/Candle/GetLive/Response.cs

@@ -3,12 +3,15 @@ namespace Application.Features.Api.Crypto.Candle.GetLive;
 public sealed record Response(Response.CandleItem? Candle)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        decimal CandleAccTradeVolume,
+        decimal CandleAccTradePrice,
+        long Timestamp,
+        string StreamType
     );
 }

+ 7 - 5
Application/Features/Api/Crypto/Candle/GetMinutes/Handler.cs

@@ -17,8 +17,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         var count = Math.Clamp(request.Count, 1, 200);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoCandle(request.Symbol, $"m{request.Unit}");
+        var cacheKey = CacheKeys.CryptoCandle(request.Market, $"m{request.Unit}");
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.CandleItem>>(cacheKey, ct);
@@ -28,18 +27,21 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var candles = await upbit.GetMinuteCandlesAsync(market, request.Unit, count, ct);
+        var candles = await upbit.GetMinuteCandlesAsync(request.Market, request.Unit, count, ct);
 
         var items = candles
             .OrderBy(c => c.CandleDateTimeUtc)
             .Select(c => new Response.CandleItem(
-                new DateTimeOffset(c.CandleDateTimeUtc, TimeSpan.Zero).ToUnixTimeSeconds(),
+                c.CandleDateTimeUtc,
+                c.CandleDateTimeKst,
                 c.OpeningPrice,
                 c.HighPrice,
                 c.LowPrice,
                 c.TradePrice,
+                c.Timestamp,
+                c.CandleAccTradePrice,
                 c.CandleAccTradeVolume,
-                c.CandleAccTradePrice
+                c.Unit ?? request.Unit
             ))
             .ToList();
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetMinutes/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetMinutes;
 
-public sealed record Query(string Symbol, int Unit, int Count = 200) : IQuery<Response>;
+public sealed record Query(string Market, int Unit, int Count = 200) : IQuery<Response>;

+ 10 - 7
Application/Features/Api/Crypto/Candle/GetMinutes/Response.cs

@@ -3,12 +3,15 @@ namespace Application.Features.Api.Crypto.Candle.GetMinutes;
 public sealed record Response(IReadOnlyList<Response.CandleItem> Candles)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        long Timestamp,
+        decimal CandleAccTradePrice,
+        decimal CandleAccTradeVolume,
+        int Unit
     );
 }

+ 7 - 5
Application/Features/Api/Crypto/Candle/GetMonths/Handler.cs

@@ -11,8 +11,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
         var count = Math.Clamp(request.Count, 1, 200);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoCandle(request.Symbol, "M");
+        var cacheKey = CacheKeys.CryptoCandle(request.Market, "M");
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.CandleItem>>(cacheKey, ct);
@@ -22,18 +21,21 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var candles = await upbit.GetMonthCandlesAsync(market, count, ct);
+        var candles = await upbit.GetMonthCandlesAsync(request.Market, count, ct);
 
         var items = candles
             .OrderBy(c => c.CandleDateTimeUtc)
             .Select(c => new Response.CandleItem(
-                new DateTimeOffset(c.CandleDateTimeUtc, TimeSpan.Zero).ToUnixTimeSeconds(),
+                c.CandleDateTimeUtc,
+                c.CandleDateTimeKst,
                 c.OpeningPrice,
                 c.HighPrice,
                 c.LowPrice,
                 c.TradePrice,
+                c.Timestamp,
+                c.CandleAccTradePrice,
                 c.CandleAccTradeVolume,
-                c.CandleAccTradePrice
+                c.FirstDayOfPeriod ?? ""
             ))
             .ToList();
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetMonths/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetMonths;
 
-public sealed record Query(string Symbol, int Count = 200) : IQuery<Response>;
+public sealed record Query(string Market, int Count = 200) : IQuery<Response>;

+ 10 - 7
Application/Features/Api/Crypto/Candle/GetMonths/Response.cs

@@ -3,12 +3,15 @@ namespace Application.Features.Api.Crypto.Candle.GetMonths;
 public sealed record Response(IReadOnlyList<Response.CandleItem> Candles)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        long Timestamp,
+        decimal CandleAccTradePrice,
+        decimal CandleAccTradeVolume,
+        string FirstDayOfPeriod
     );
 }

+ 7 - 6
Application/Features/Api/Crypto/Candle/GetSeconds/Handler.cs

@@ -11,8 +11,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
         var count = Math.Clamp(request.Count, 1, 200);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoCandle(request.Symbol, "s");
+        var cacheKey = CacheKeys.CryptoCandle(request.Market, "s");
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.CandleItem>>(cacheKey, ct);
@@ -22,18 +21,20 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var candles = await upbit.GetSecondCandlesAsync(market, count, ct);
+        var candles = await upbit.GetSecondCandlesAsync(request.Market, count, ct);
 
         var items = candles
             .OrderBy(c => c.CandleDateTimeUtc)
             .Select(c => new Response.CandleItem(
-                new DateTimeOffset(c.CandleDateTimeUtc, TimeSpan.Zero).ToUnixTimeSeconds(),
+                c.CandleDateTimeUtc,
+                c.CandleDateTimeKst,
                 c.OpeningPrice,
                 c.HighPrice,
                 c.LowPrice,
                 c.TradePrice,
-                c.CandleAccTradeVolume,
-                c.CandleAccTradePrice
+                c.Timestamp,
+                c.CandleAccTradePrice,
+                c.CandleAccTradeVolume
             ))
             .ToList();
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetSeconds/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetSeconds;
 
-public sealed record Query(string Symbol, int Count = 200) : IQuery<Response>;
+public sealed record Query(string Market, int Count = 200) : IQuery<Response>;

+ 9 - 7
Application/Features/Api/Crypto/Candle/GetSeconds/Response.cs

@@ -3,12 +3,14 @@ namespace Application.Features.Api.Crypto.Candle.GetSeconds;
 public sealed record Response(IReadOnlyList<Response.CandleItem> Candles)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        long Timestamp,
+        decimal CandleAccTradePrice,
+        decimal CandleAccTradeVolume
     );
 }

+ 7 - 5
Application/Features/Api/Crypto/Candle/GetWeeks/Handler.cs

@@ -11,8 +11,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
         var count = Math.Clamp(request.Count, 1, 200);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoCandle(request.Symbol, "w");
+        var cacheKey = CacheKeys.CryptoCandle(request.Market, "w");
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.CandleItem>>(cacheKey, ct);
@@ -22,18 +21,21 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var candles = await upbit.GetWeekCandlesAsync(market, count, ct);
+        var candles = await upbit.GetWeekCandlesAsync(request.Market, count, ct);
 
         var items = candles
             .OrderBy(c => c.CandleDateTimeUtc)
             .Select(c => new Response.CandleItem(
-                new DateTimeOffset(c.CandleDateTimeUtc, TimeSpan.Zero).ToUnixTimeSeconds(),
+                c.CandleDateTimeUtc,
+                c.CandleDateTimeKst,
                 c.OpeningPrice,
                 c.HighPrice,
                 c.LowPrice,
                 c.TradePrice,
+                c.Timestamp,
+                c.CandleAccTradePrice,
                 c.CandleAccTradeVolume,
-                c.CandleAccTradePrice
+                c.FirstDayOfPeriod ?? ""
             ))
             .ToList();
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetWeeks/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetWeeks;
 
-public sealed record Query(string Symbol, int Count = 200) : IQuery<Response>;
+public sealed record Query(string Market, int Count = 200) : IQuery<Response>;

+ 10 - 7
Application/Features/Api/Crypto/Candle/GetWeeks/Response.cs

@@ -3,12 +3,15 @@ namespace Application.Features.Api.Crypto.Candle.GetWeeks;
 public sealed record Response(IReadOnlyList<Response.CandleItem> Candles)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        long Timestamp,
+        decimal CandleAccTradePrice,
+        decimal CandleAccTradeVolume,
+        string FirstDayOfPeriod
     );
 }

+ 7 - 5
Application/Features/Api/Crypto/Candle/GetYears/Handler.cs

@@ -11,8 +11,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
         var count = Math.Clamp(request.Count, 1, 200);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoCandle(request.Symbol, "y");
+        var cacheKey = CacheKeys.CryptoCandle(request.Market, "y");
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.CandleItem>>(cacheKey, ct);
@@ -22,18 +21,21 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var candles = await upbit.GetYearCandlesAsync(market, count, ct);
+        var candles = await upbit.GetYearCandlesAsync(request.Market, count, ct);
 
         var items = candles
             .OrderBy(c => c.CandleDateTimeUtc)
             .Select(c => new Response.CandleItem(
-                new DateTimeOffset(c.CandleDateTimeUtc, TimeSpan.Zero).ToUnixTimeSeconds(),
+                c.CandleDateTimeUtc,
+                c.CandleDateTimeKst,
                 c.OpeningPrice,
                 c.HighPrice,
                 c.LowPrice,
                 c.TradePrice,
+                c.Timestamp,
+                c.CandleAccTradePrice,
                 c.CandleAccTradeVolume,
-                c.CandleAccTradePrice
+                c.FirstDayOfPeriod ?? ""
             ))
             .ToList();
 

+ 1 - 1
Application/Features/Api/Crypto/Candle/GetYears/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Candle.GetYears;
 
-public sealed record Query(string Symbol, int Count = 200) : IQuery<Response>;
+public sealed record Query(string Market, int Count = 200) : IQuery<Response>;

+ 10 - 7
Application/Features/Api/Crypto/Candle/GetYears/Response.cs

@@ -3,12 +3,15 @@ namespace Application.Features.Api.Crypto.Candle.GetYears;
 public sealed record Response(IReadOnlyList<Response.CandleItem> Candles)
 {
     public sealed record CandleItem(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        long Timestamp,
+        decimal CandleAccTradePrice,
+        decimal CandleAccTradeVolume,
+        string FirstDayOfPeriod
     );
 }

+ 12 - 5
Application/Features/Api/Crypto/Market/GetAll/Handler.cs

@@ -22,14 +22,21 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         // Upbit REST API 호출
         var markets = await upbit.GetMarketsAsync(ct);
 
-        // KRW 마켓만 필터링
         var items = markets
-            .Where(m => m.Market.StartsWith("KRW-"))
             .Select(m => new Response.MarketItem(
                 m.Market,
-                m.KorName,
-                m.EngName,
-                m.MarketWarning
+                m.KoreanName,
+                m.EnglishName,
+                new Response.MarketEvent(
+                    m.MarketEvent.Warning,
+                    new Response.MarketCaution(
+                        m.MarketEvent.Caution.PriceFluctuations,
+                        m.MarketEvent.Caution.TradingVolumeSoaring,
+                        m.MarketEvent.Caution.DepositAmountSoaring,
+                        m.MarketEvent.Caution.GlobalPriceDifferences,
+                        m.MarketEvent.Caution.ConcentrationOfSmallAccounts
+                    )
+                )
             ))
             .ToList();
 

+ 16 - 3
Application/Features/Api/Crypto/Market/GetAll/Response.cs

@@ -4,8 +4,21 @@ public sealed record Response(IReadOnlyList<Response.MarketItem> Markets)
 {
     public sealed record MarketItem(
         string Market,
-        string KorName,
-        string EngName,
-        string? MarketWarning
+        string KoreanName,
+        string EnglishName,
+        MarketEvent MarketEvent
+    );
+
+    public sealed record MarketEvent(
+        bool Warning,
+        MarketCaution Caution
+    );
+
+    public sealed record MarketCaution(
+        bool PriceFluctuations,
+        bool TradingVolumeSoaring,
+        bool DepositAmountSoaring,
+        bool GlobalPriceDifferences,
+        bool ConcentrationOfSmallAccounts
     );
 }

+ 5 - 5
Application/Features/Api/Crypto/Orderbook/Get/Handler.cs

@@ -10,8 +10,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
 
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoOrderbook(request.Symbol);
+        var cacheKey = CacheKeys.CryptoOrderbook(request.Market);
 
         // 캐시 확인
         var cached = await cache.GetAsync<Response>(cacheKey, ct);
@@ -21,10 +20,10 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var orderbooks = await upbit.GetOrderbookAsync([market], ct);
+        var orderbooks = await upbit.GetOrderbookAsync([request.Market], ct);
         if (orderbooks.Count == 0)
         {
-            return new Response(0, 0, [], 0);
+            return new Response(0, 0, [], 0, 0);
         }
 
         var o = orderbooks[0];
@@ -37,7 +36,8 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
                 u.AskSize,
                 u.BidSize
             ))],
-            o.Timestamp
+            o.Timestamp,
+            o.Level
         );
 
         // Redis 캐시 저장 (5초)

+ 1 - 1
Application/Features/Api/Crypto/Orderbook/Get/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Orderbook.Get;
 
-public sealed record Query(string Symbol) : IQuery<Response>;
+public sealed record Query(string Market) : IQuery<Response>;

+ 2 - 1
Application/Features/Api/Crypto/Orderbook/Get/Response.cs

@@ -4,7 +4,8 @@ public sealed record Response(
     decimal TotalAskSize,
     decimal TotalBidSize,
     IReadOnlyList<Response.Unit> Units,
-    long Timestamp
+    long Timestamp,
+    decimal Level
 )
 {
     public sealed record Unit(

+ 2 - 2
Application/Features/Api/Crypto/Orderbook/GetLive/Handler.cs

@@ -7,10 +7,10 @@ public sealed class Handler(ICacheService cache) : IQueryHandler<Query, Response
 {
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
-        var cacheKey = CacheKeys.CryptoOrderbookLive(request.Symbol);
+        var cacheKey = CacheKeys.CryptoOrderbookLive(request.Market);
 
         var cached = await cache.GetAsync<Response>(cacheKey, ct);
 
-        return cached ?? new Response(0, 0, [], 0);
+        return cached ?? new Response(0, 0, [], 0, 0, "");
     }
 }

+ 1 - 1
Application/Features/Api/Crypto/Orderbook/GetLive/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Orderbook.GetLive;
 
-public sealed record Query(string Symbol) : IQuery<Response>;
+public sealed record Query(string Market) : IQuery<Response>;

+ 3 - 1
Application/Features/Api/Crypto/Orderbook/GetLive/Response.cs

@@ -4,7 +4,9 @@ public sealed record Response(
     decimal TotalAskSize,
     decimal TotalBidSize,
     IReadOnlyList<Response.Unit> Units,
-    long Timestamp
+    long Timestamp,
+    decimal Level,
+    string StreamType
 )
 {
     public sealed record Unit(

+ 27 - 29
Application/Features/Api/Crypto/Ticker/GetAll/Handler.cs

@@ -10,54 +10,52 @@ public sealed class Handler(IAppDbContext db, ICacheService cache) : IQueryHandl
 {
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
+        var quote = string.IsNullOrWhiteSpace(request.Quote) ? "KRW" : request.Quote.ToUpper();
+
         // Redis에서 Ticker 목록 가져오기
-        var tickers = await cache.GetAsync<List<UpbitTicker>>(CacheKeys.CryptoTickers, ct) ?? [];
+        var tickers = await cache.GetAsync<List<UpbitTicker>>(CacheKeys.CryptoTickers(quote), ct) ?? [];
         var tickerMap = tickers.ToDictionary(t => t.Market, t => t);
 
-        // DB에서 코인 정보 가져오기
-        var coinsQuery = db.Coin
-            .AsNoTracking()
-            .Where(c => c.IsActive && !c.IsDelisted);
+        // DB에서 CoinMarket + Coin 정보 가져오기
+        var prefix = $"{quote}-";
+        var query = db.CoinMarket.AsNoTracking().Include(cm => cm.Coin).Where(cm => cm.Market.StartsWith(prefix) && cm.Coin.IsActive && !cm.Coin.IsDelisted);
 
         if (request.FeaturedOnly)
         {
-            coinsQuery = coinsQuery.Where(c => c.IsFeatured);
+            query = query.Where(cm => cm.Coin.IsFeatured);
         }
 
-        var coins = await coinsQuery
-            .OrderByDescending(c => c.IsFeatured)
-            .ThenBy(c => c.DisplayOrder)
-            .ThenBy(c => c.Symbol)
-            .Select(c => new
-            {
-                c.Symbol,
-                c.KorName,
-                c.EngName,
-                c.LogoImage,
-                c.IsFeatured,
-                c.DisplayOrder
-            })
-            .ToListAsync(ct);
+        var coinMarkets = await query.OrderByDescending(cm => cm.Coin.IsFeatured).ThenBy(cm => cm.Coin.DisplayOrder).ThenBy(cm => cm.Coin.Symbol).Select(cm => new { cm.Market, cm.Coin.Symbol, cm.Coin.KorName, cm.Coin.EngName, cm.Coin.LogoImage, cm.Coin.IsFeatured, cm.Coin.DisplayOrder }).ToListAsync(ct);
 
         var rows = new List<Response.Row>();
 
-        foreach (var coin in coins)
+        foreach (var cm in coinMarkets)
         {
-            var market = $"KRW-{coin.Symbol.ToUpper()}";
-            tickerMap.TryGetValue(market, out var ticker);
+            tickerMap.TryGetValue(cm.Market, out var ticker);
 
             rows.Add(new Response.Row(
-                coin.Symbol,
-                coin.KorName,
-                coin.EngName,
-                coin.LogoImage,
+                cm.Market,
+                cm.Symbol,
+                cm.KorName,
+                cm.EngName,
+                cm.LogoImage,
+                ticker?.OpeningPrice ?? 0m,
+                ticker?.HighPrice ?? 0m,
+                ticker?.LowPrice ?? 0m,
                 ticker?.TradePrice ?? 0m,
+                ticker?.PrevClosingPrice ?? 0m,
                 ticker?.Change ?? "",
+                ticker?.ChangePrice ?? 0m,
                 ticker?.SignedChangePrice ?? 0m,
+                ticker?.ChangeRate ?? 0m,
                 ticker?.SignedChangeRate ?? 0m,
+                ticker?.TradeVolume ?? 0m,
+                ticker?.AccTradeVolume ?? 0m,
+                ticker?.AccTradeVolume24h ?? 0m,
+                ticker?.AccTradePrice ?? 0m,
                 ticker?.AccTradePrice24h ?? 0m,
-                coin.IsFeatured,
-                coin.DisplayOrder
+                cm.IsFeatured,
+                cm.DisplayOrder
             ));
         }
 

+ 1 - 1
Application/Features/Api/Crypto/Ticker/GetAll/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Ticker.GetAll;
 
-public sealed record Query(bool FeaturedOnly = false) : IQuery<Response>;
+public sealed record Query(string? Quote = null, bool FeaturedOnly = false) : IQuery<Response>;

+ 11 - 0
Application/Features/Api/Crypto/Ticker/GetAll/Response.cs

@@ -3,14 +3,25 @@ namespace Application.Features.Api.Crypto.Ticker.GetAll;
 public sealed record Response(IReadOnlyList<Response.Row> Tickers)
 {
     public sealed record Row(
+        string Market,
         string Symbol,
         string KorName,
         string EngName,
         string? LogoImage,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
         decimal TradePrice,
+        decimal PrevClosingPrice,
         string Change,
+        decimal ChangePrice,
         decimal SignedChangePrice,
+        decimal ChangeRate,
         decimal SignedChangeRate,
+        decimal TradeVolume,
+        decimal AccTradeVolume,
+        decimal AccTradeVolume24h,
+        decimal AccTradePrice,
         decimal AccTradePrice24h,
         bool IsFeatured,
         short DisplayOrder

+ 15 - 8
Application/Features/Api/Crypto/Ticker/GetDetail/Handler.cs

@@ -10,8 +10,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
 
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoTickerDetail(request.Symbol);
+        var cacheKey = CacheKeys.CryptoTickerDetail(request.Market);
 
         // 캐시 확인
         var cached = await cache.GetAsync<Response>(cacheKey, ct);
@@ -21,23 +20,31 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var tickers = await upbit.GetTickersAsync([market], ct);
+        var tickers = await upbit.GetTickersAsync([request.Market], ct);
         if (tickers.Count == 0)
         {
-            return new Response(market, 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, "", 0);
+            return new Response(request.Market, "", "", "", "", 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, "", 0);
         }
 
         var t = tickers[0];
         var response = new Response(
             t.Market,
-            t.TradePrice,
-            t.Change,
-            t.SignedChangePrice,
-            t.SignedChangeRate,
+            t.TradeDate,
+            t.TradeTime,
+            t.TradeDateKst,
+            t.TradeTimeKst,
+            t.TradeTimestamp,
             t.OpeningPrice,
             t.HighPrice,
             t.LowPrice,
+            t.TradePrice,
             t.PrevClosingPrice,
+            t.Change,
+            t.ChangePrice,
+            t.ChangeRate,
+            t.SignedChangePrice,
+            t.SignedChangeRate,
+            t.TradeVolume,
             t.AccTradePrice,
             t.AccTradePrice24h,
             t.AccTradeVolume,

+ 1 - 1
Application/Features/Api/Crypto/Ticker/GetDetail/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Ticker.GetDetail;
 
-public sealed record Query(string Symbol) : IQuery<Response>;
+public sealed record Query(string Market) : IQuery<Response>;

+ 12 - 4
Application/Features/Api/Crypto/Ticker/GetDetail/Response.cs

@@ -2,14 +2,22 @@ namespace Application.Features.Api.Crypto.Ticker.GetDetail;
 
 public sealed record Response(
     string Market,
-    decimal TradePrice,
-    string Change,
-    decimal SignedChangePrice,
-    decimal SignedChangeRate,
+    string TradeDate,
+    string TradeTime,
+    string TradeDateKst,
+    string TradeTimeKst,
+    long TradeTimestamp,
     decimal OpeningPrice,
     decimal HighPrice,
     decimal LowPrice,
+    decimal TradePrice,
     decimal PrevClosingPrice,
+    string Change,
+    decimal ChangePrice,
+    decimal ChangeRate,
+    decimal SignedChangePrice,
+    decimal SignedChangeRate,
+    decimal TradeVolume,
     decimal AccTradePrice,
     decimal AccTradePrice24h,
     decimal AccTradeVolume,

+ 1 - 1
Application/Features/Api/Crypto/Trade/GetLive/Handler.cs

@@ -7,7 +7,7 @@ public sealed class Handler(ICacheService cache) : IQueryHandler<Query, Response
 {
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
-        var cacheKey = CacheKeys.CryptoTradeLive(request.Symbol);
+        var cacheKey = CacheKeys.CryptoTradeLive(request.Market);
 
         var cached = await cache.GetAsync<Response.TradeItem>(cacheKey, ct);
 

+ 1 - 1
Application/Features/Api/Crypto/Trade/GetLive/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Trade.GetLive;
 
-public sealed record Query(string Symbol) : IQuery<Response>;
+public sealed record Query(string Market) : IQuery<Response>;

+ 14 - 2
Application/Features/Api/Crypto/Trade/GetLive/Response.cs

@@ -3,9 +3,21 @@ namespace Application.Features.Api.Crypto.Trade.GetLive;
 public sealed record Response(Response.TradeItem? Trade)
 {
     public sealed record TradeItem(
-        long Timestamp,
         decimal TradePrice,
         decimal TradeVolume,
-        string AskBid
+        string AskBid,
+        decimal PrevClosingPrice,
+        string Change,
+        decimal ChangePrice,
+        string TradeDate,
+        string TradeTime,
+        long TradeTimestamp,
+        long SequentialId,
+        long Timestamp,
+        string StreamType,
+        decimal BestAskPrice,
+        decimal BestAskSize,
+        decimal BestBidPrice,
+        decimal BestBidSize
     );
 }

+ 4 - 3
Application/Features/Api/Crypto/Trade/GetRecent/Handler.cs

@@ -11,8 +11,7 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
     public async Task<Response> Handle(Query request, CancellationToken ct)
     {
         var count = Math.Clamp(request.Count, 1, 100);
-        var market = $"KRW-{request.Symbol.ToUpper()}";
-        var cacheKey = CacheKeys.CryptoTrades(request.Symbol);
+        var cacheKey = CacheKeys.CryptoTrades(request.Market);
 
         // 캐시 확인
         var cached = await cache.GetAsync<List<Response.TradeItem>>(cacheKey, ct);
@@ -22,13 +21,15 @@ public sealed class Handler(IUpbitClient upbit, ICacheService cache) : IQueryHan
         }
 
         // Upbit REST API 호출
-        var trades = await upbit.GetTradesAsync(market, count, ct);
+        var trades = await upbit.GetTradesAsync(request.Market, count, ct);
 
         var items = trades
             .Select(t => new Response.TradeItem(
                 t.Timestamp,
                 t.TradePrice,
                 t.TradeVolume,
+                t.PrevClosingPrice,
+                t.ChangePrice,
                 t.AskBid,
                 t.SequentialId
             ))

+ 1 - 1
Application/Features/Api/Crypto/Trade/GetRecent/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Crypto.Trade.GetRecent;
 
-public sealed record Query(string Symbol, int Count = 50) : IQuery<Response>;
+public sealed record Query(string Market, int Count = 50) : IQuery<Response>;

+ 2 - 0
Application/Features/Api/Crypto/Trade/GetRecent/Response.cs

@@ -6,6 +6,8 @@ public sealed record Response(IReadOnlyList<Response.TradeItem> Trades)
         long Timestamp,
         decimal TradePrice,
         decimal TradeVolume,
+        decimal PrevClosingPrice,
+        decimal ChangePrice,
         string AskBid,
         long SequentialId
     );

+ 5 - 0
Directory.Build.props

@@ -0,0 +1,5 @@
+<Project>
+  <PropertyGroup>
+    <NoWarn>$(NoWarn);NU1903</NoWarn>
+  </PropertyGroup>
+</Project>

+ 30 - 0
Domain/Entities/Crypto/Coin.cs

@@ -6,6 +6,8 @@ namespace Domain.Entities.Crypto
     {
         public virtual List<CoinCategoryMap> CoinCategoryMap { get; private set; } = [];
 
+        public virtual List<CoinMarket> CoinMarket { get; private set; } = [];
+
         [Key]
         public int ID { get; private set; }
 
@@ -196,5 +198,33 @@ namespace Domain.Entities.Crypto
             DisplayOrder = displayOrder;
             UpdatedAt = DateTime.UtcNow;
         }
+
+        // Upbit 마켓 데이터로 동기화 (신규 생성용)
+        public static Coin SyncFromUpbit(string symbol, string korName, string engName, bool isWarning)
+        {
+            return new(symbol, korName, engName, null, null, null, null, null, null, isActive: true, isWarning, isNew: false, isDelisted: false);
+        }
+
+        // 상장폐지 처리
+        public void MarkDelisted()
+        {
+            IsDelisted = true;
+            IsActive = false;
+            UpdatedAt = DateTime.UtcNow;
+        }
+
+        // 동기화 (이름/경고 상태 갱신, 상폐 해제)
+        public void SyncUpdate(string korName, string engName, bool isWarning)
+        {
+            KorName = korName;
+            EngName = engName;
+            IsWarning = isWarning;
+            if (IsDelisted)
+            {
+                IsDelisted = false;
+                IsActive = true;
+            }
+            UpdatedAt = DateTime.UtcNow;
+        }
     }
 }

+ 18 - 0
Domain/Entities/Crypto/CoinMarket.cs

@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Domain.Entities.Crypto
+{
+    public class CoinMarket
+    {
+        [ForeignKey(nameof(CoinID))]
+        public virtual Coin Coin { get; set; } = null!;
+
+        [Key]
+        public int ID { get; set; }
+
+        public int CoinID { get; set; }
+
+        public string Market { get; set; } = default!;
+    }
+}

+ 123 - 62
Infrastructure/Crypto/UpbitRestClient.cs

@@ -14,77 +14,99 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
     };
 
     // ─── Candle ───────────────────────────────────────────────
-
+    // 초 단위
     public async Task<IReadOnlyList<UpbitCandle>> GetSecondCandlesAsync(string market, int count, CancellationToken ct = default)
     {
         return await FetchCandlesAsync($"candles/seconds?market={market}&count={count}", ct);
     }
 
+    // 분 단위
     public async Task<IReadOnlyList<UpbitCandle>> GetMinuteCandlesAsync(string market, int unit, int count, CancellationToken ct = default)
     {
         return await FetchCandlesAsync($"candles/minutes/{unit}?market={market}&count={count}", ct);
     }
 
+    // 일 단위
     public async Task<IReadOnlyList<UpbitCandle>> GetDayCandlesAsync(string market, int count, CancellationToken ct = default)
     {
         return await FetchCandlesAsync($"candles/days?market={market}&count={count}", ct);
     }
 
+    // 주 단위
     public async Task<IReadOnlyList<UpbitCandle>> GetWeekCandlesAsync(string market, int count, CancellationToken ct = default)
     {
         return await FetchCandlesAsync($"candles/weeks?market={market}&count={count}", ct);
     }
 
+    // 월 단위
     public async Task<IReadOnlyList<UpbitCandle>> GetMonthCandlesAsync(string market, int count, CancellationToken ct = default)
     {
         return await FetchCandlesAsync($"candles/months?market={market}&count={count}", ct);
     }
 
+    // 연 단위
     public async Task<IReadOnlyList<UpbitCandle>> GetYearCandlesAsync(string market, int count, CancellationToken ct = default)
     {
         return await FetchCandlesAsync($"candles/years?market={market}&count={count}", ct);
     }
 
-    // ─── Market ───────────────────────────────────────────────
+    // ─── Paires/Market ───────────────────────────────────────────────
 
     public async Task<IReadOnlyList<UpbitMarket>> GetMarketsAsync(CancellationToken ct = default)
     {
         var items = await http.GetFromJsonAsync<List<UpbitMarketDto>>("market/all?is_details=true", _jsonOptions, ct) ?? [];
 
         return [..items.Select(m => new UpbitMarket(
-            m.Market,
-            m.KoreanName,
-            m.EnglishName,
-            m.MarketWarning
+            m.Market,                                                             // 페어(거래쌍)의 코드
+            m.KoreanName,                                                         // 가상자산의 한글명
+            m.EnglishName,                                                        // 가상자산의 영문명
+            new UpbitMarketEvent(                                                 // 종목 경보 정보
+                m.MarketEvent?.Warning ?? false,                                  // 유의 종목 여부
+                new UpbitMarketCaution(                                           // 주의 종목 여부
+                    m.MarketEvent?.Caution?.PriceFluctuations ?? false,           // 가격 급등락 경보
+                    m.MarketEvent?.Caution?.TradingVolumeSoaring ?? false,        // 거래량 급증 경보
+                    m.MarketEvent?.Caution?.DepositAmountSoaring ?? false,        // 입금량 급증 경보
+                    m.MarketEvent?.Caution?.GlobalPriceDifferences ?? false,      // 국내외 가격 차이 경보
+                    m.MarketEvent?.Caution?.ConcentrationOfSmallAccounts ?? false // 소수 계정 집중 거래 경보
+                )
+            )
         ))];
     }
 
     // ─── Ticker ───────────────────────────────────────────────
-
+    // 현재가
     public async Task<IReadOnlyList<UpbitTickerDetail>> GetTickersAsync(string[] markets, CancellationToken ct = default)
     {
         var query = string.Join(",", markets);
         var items = await http.GetFromJsonAsync<List<UpbitTickerDetailDto>>($"ticker?markets={query}", _jsonOptions, ct) ?? [];
 
         return [..items.Select(t => new UpbitTickerDetail(
-            t.Market,
-            t.TradePrice,
-            t.Change,
-            t.SignedChangePrice,
-            t.SignedChangeRate,
-            t.OpeningPrice,
-            t.HighPrice,
-            t.LowPrice,
-            t.PrevClosingPrice,
-            t.AccTradePrice,
-            t.AccTradePrice24h,
-            t.AccTradeVolume,
-            t.AccTradeVolume24h,
-            t.Highest52WeekPrice,
-            t.Highest52WeekDate,
-            t.Lowest52WeekPrice,
-            t.Lowest52WeekDate,
-            t.Timestamp
+            t.Market,             // 페어(거래쌍) 코드
+            t.TradeDate,          // 체근 체결 일자(UTC)
+            t.TradeTime,          // 체근 체결 시작 (UTC)
+            t.TradeDateKst,       // 체근 체결 일자(KST)
+            t.TradeTimeKst,       // 체근 체결 시각(KST)
+            t.TradeTimestamp,     // 체결 시각의 밀리초단위 타임스탬프
+            t.OpeningPrice,       // 해당 페어의 첫 거래 가격
+            t.HighPrice,          // 해당 페어의 최고 거래 가격
+            t.LowPrice,           // 해덩 페어의 최저 거래 가격
+            t.TradePrice,         // 해당 페어의 현재 가격
+            t.PrevClosingPrice,   // 해당 페어의 전일 종가
+            t.Change,             // 가격 변동 상태(EVEN: 보합, RISE: 항승, FALL: 하락)
+            t.ChangePrice,        // 전일 종가 대비 가격 변화
+            t.ChangeRate,         // 전일 종가 대비 가격 변화율
+            t.SignedChangePrice,  // 전일 종가 대비 가격 변화 (부호 있는)(RISE: +, FALL: -)
+            t.SignedChangeRate,   // 전일 종가 대비 가격 변화율
+            t.TradeVolume,        // 최근 거래 수량
+            t.AccTradePrice,      // 누적 거래 금액(UTC)
+            t.AccTradePrice24h,   // 24시간 누적 거래 금액
+            t.AccTradeVolume,     // 누적 거래량(UTC)
+            t.AccTradeVolume24h,  // 24시간 누적 거래량
+            t.Highest52WeekPrice, // 52주 신고가
+            t.Highest52WeekDate,  // 52주 신고가 달성일
+            t.Lowest52WeekPrice,  // 52주 신저가
+            t.Lowest52WeekDate,   // 52주 신저가 달성일
+            t.Timestamp           // 현재가 정보가 반영된 시각의 시간(mm)
         ))];
     }
 
@@ -95,15 +117,16 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
         var items = await http.GetFromJsonAsync<List<UpbitTradeDto>>($"trades/ticks?market={market}&count={count}", _jsonOptions, ct) ?? [];
 
         return [..items.Select(t => new UpbitTrade(
-            t.Market,
-            t.TradeDate,
-            t.TradeTime,
-            t.Timestamp,
-            t.TradePrice,
-            t.TradeVolume,
-            t.PrevClosingPrice,
-            t.AskBid,
-            t.SequentialId
+            t.Market,           // 페어(거래쌍)의 코드
+            t.TradeDateUtc,     // 체결 일자(UTC)
+            t.TradeTimeUtc,     // 체결 시각(UTC)
+            t.Timestamp,        // 체결 시간(mm)
+            t.TradePrice,       // 최근 체결 가격
+            t.TradeVolume,      // 최근 체결량
+            t.PrevClosingPrice, // 전일 종가
+            t.ChangePrice,      // 전일 종가 대비 가격 변화
+            t.AskBid,           // 매수/매도 구분(BID: 매수, ASK: 매도)
+            t.SequentialId      // 체결 번호(Unique)
         ))];
     }
 
@@ -115,36 +138,43 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
         var items = await http.GetFromJsonAsync<List<UpbitOrderbookDto>>($"orderbook?markets={query}", _jsonOptions, ct) ?? [];
 
         return [..items.Select(o => new UpbitOrderbook(
-            o.Market,
-            o.TotalAskSize,
-            o.TotalBidSize,
-            [..o.OrderbookUnits.Select(u => new UpbitOrderbookUnit(
-                u.AskPrice,
-                u.BidPrice,
-                u.AskSize,
-                u.BidSize
+            o.Market,                                               // 페어(거래쌍)의 코드
+            o.TotalAskSize,                                         // 현재 호가의 전체 매도 잔량 합계
+            o.TotalBidSize,                                         // 현재 호가의 전체 매수 잔량 합계
+            [..o.OrderbookUnits.Select(u => new UpbitOrderbookUnit( // 호가 정보가 담긴 목록 (총 30호)
+                u.AskPrice,                                         // 매도 호가
+                u.BidPrice,                                         // 매수 호가
+                u.AskSize,                                          // 매도 잔량
+                u.BidSize                                           // 매수 잔량
             ))],
-            o.Timestamp
+            o.Timestamp,                                            // 조회 요청 시간(ms)
+            o.Level                                                 // 호가가 적용된 가격 단위
         ))];
     }
 
     // ─── Private helpers ──────────────────────────────────────
-
+    // 캔들 정보 조회
     private async Task<IReadOnlyList<UpbitCandle>> FetchCandlesAsync(string url, CancellationToken ct)
     {
         var items = await http.GetFromJsonAsync<List<UpbitCandleDto>>(url, _jsonOptions, ct) ?? [];
 
         return [..items.Select(c => new UpbitCandle(
-            c.Market,
-            c.CandleDateTimeUtc,
-            c.OpeningPrice,
-            c.HighPrice,
-            c.LowPrice,
-            c.TradePrice,
-            c.Timestamp,
-            c.CandleAccTradePrice,
-            c.CandleAccTradeVolume,
-            c.FirstDayOfPeriod
+            c.Market,                // 페어(거래쌍)의 코드
+            c.CandleDateTimeUtc,     // 캔들 기준 시각 (UTC)
+            c.CandleDateTimeKst,     // 캔들 기준 시각 (KST)
+            c.OpeningPrice,          // 시가
+            c.HighPrice,             // 고가
+            c.LowPrice,              // 저가
+            c.TradePrice,            // 종가 (현재가)
+            c.Timestamp,             // 마지막 틱이 저장된 시각 (ms)
+            c.CandleAccTradePrice,   // 누적 거래 금액
+            c.CandleAccTradeVolume,  // 누적 거래량
+            c.Unit,                  // 캔들 집계 시간 단위 (분)
+            c.PrevClosingPrice,      // 전일 종가 (UTC 0시 기준)
+            c.ChangePrice,           // 전일 종가 대비 가격 변화
+            c.ChangeRate,            // 전일 종가 대비 가격 변화율
+            c.ConvertedTradePrice,   // 종가 환산 화폐 단위로 환산된 가격
+            c.FirstDayOfPeriod       // 캔들 집계 시작일자
         ))];
     }
 
@@ -153,7 +183,8 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
     private sealed class UpbitCandleDto
     {
         public string Market { get; set; } = default!;
-        public DateTime CandleDateTimeUtc { get; set; }
+        public string CandleDateTimeUtc { get; set; } = default!;
+        public string CandleDateTimeKst { get; set; } = default!;
         public decimal OpeningPrice { get; set; }
         public decimal HighPrice { get; set; }
         public decimal LowPrice { get; set; }
@@ -161,6 +192,11 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
         public long Timestamp { get; set; }
         public decimal CandleAccTradePrice { get; set; }
         public decimal CandleAccTradeVolume { get; set; }
+        public int? Unit { get; set; }
+        public decimal? PrevClosingPrice { get; set; }
+        public decimal? ChangePrice { get; set; }
+        public decimal? ChangeRate { get; set; }
+        public decimal? ConvertedTradePrice { get; set; }
         public string? FirstDayOfPeriod { get; set; }
     }
 
@@ -169,20 +205,43 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
         public string Market { get; set; } = default!;
         public string KoreanName { get; set; } = default!;
         public string EnglishName { get; set; } = default!;
-        public string? MarketWarning { get; set; }
+        public UpbitMarketEventDto? MarketEvent { get; set; }
+    }
+
+    private sealed class UpbitMarketEventDto
+    {
+        public bool Warning { get; set; }
+        public UpbitMarketCautionDto? Caution { get; set; }
+    }
+
+    private sealed class UpbitMarketCautionDto
+    {
+        public bool PriceFluctuations { get; set; }
+        public bool TradingVolumeSoaring { get; set; }
+        public bool DepositAmountSoaring { get; set; }
+        public bool GlobalPriceDifferences { get; set; }
+        public bool ConcentrationOfSmallAccounts { get; set; }
     }
 
     private sealed class UpbitTickerDetailDto
     {
         public string Market { get; set; } = default!;
-        public decimal TradePrice { get; set; }
-        public string Change { get; set; } = default!;
-        public decimal SignedChangePrice { get; set; }
-        public decimal SignedChangeRate { get; set; }
+        public string TradeDate { get; set; } = default!;
+        public string TradeTime { get; set; } = default!;
+        public string TradeDateKst { get; set; } = default!;
+        public string TradeTimeKst { get; set; } = default!;
+        public long TradeTimestamp { get; set; }
         public decimal OpeningPrice { get; set; }
         public decimal HighPrice { get; set; }
         public decimal LowPrice { get; set; }
+        public decimal TradePrice { get; set; }
         public decimal PrevClosingPrice { get; set; }
+        public string Change { get; set; } = default!;
+        public decimal ChangePrice { get; set; }
+        public decimal ChangeRate { get; set; }
+        public decimal SignedChangePrice { get; set; }
+        public decimal SignedChangeRate { get; set; }
+        public decimal TradeVolume { get; set; }
         public decimal AccTradePrice { get; set; }
         public decimal AccTradePrice24h { get; set; }
         public decimal AccTradeVolume { get; set; }
@@ -197,12 +256,13 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
     private sealed class UpbitTradeDto
     {
         public string Market { get; set; } = default!;
-        public string TradeDate { get; set; } = default!;
-        public string TradeTime { get; set; } = default!;
+        public string TradeDateUtc { get; set; } = default!;
+        public string TradeTimeUtc { get; set; } = default!;
         public long Timestamp { get; set; }
         public decimal TradePrice { get; set; }
         public decimal TradeVolume { get; set; }
         public decimal PrevClosingPrice { get; set; }
+        public decimal ChangePrice { get; set; }
         public string AskBid { get; set; } = default!;
         public long SequentialId { get; set; }
     }
@@ -214,6 +274,7 @@ public sealed class UpbitRestClient(HttpClient http) : IUpbitClient
         public decimal TotalBidSize { get; set; }
         public List<UpbitOrderbookUnitDto> OrderbookUnits { get; set; } = [];
         public long Timestamp { get; set; }
+        public decimal Level { get; set; }
     }
 
     private sealed class UpbitOrderbookUnitDto

+ 232 - 57
Infrastructure/Crypto/UpbitWebSocketService.cs

@@ -15,9 +15,10 @@ namespace Infrastructure.Crypto;
 public sealed class UpbitWebSocketService(
     IServiceScopeFactory scopeFactory,
     ICacheService cache,
+    ICryptoHubService hub,
     ILogger<UpbitWebSocketService> logger
-) : BackgroundService
-{
+) : BackgroundService {
+
     private static readonly Uri _wsUri = new("wss://api.upbit.com/websocket/v1");
     private static readonly TimeSpan _reconnectDelay = TimeSpan.FromSeconds(5);
     private static readonly TimeSpan _pingInterval = TimeSpan.FromSeconds(60);
@@ -52,16 +53,18 @@ public sealed class UpbitWebSocketService(
         var marketCodes = await GetMarketCodesAsync(ct);
         if (marketCodes.Count == 0)
         {
-            logger.LogInformation("활성 코인이 없습니다. 30초 후 재확인...");
+            logger.LogInformation("활성 거래쌍이 없습니다. 30초 후 재확인...");
 
             await Task.Delay(TimeSpan.FromSeconds(30), ct);
             return;
         }
 
+        var uniqueQuotes = marketCodes.Select(x => x.Split('-')[0]).Distinct().Count();
+
         using var ws = new ClientWebSocket();
         await ws.ConnectAsync(_wsUri, ct);
 
-        logger.LogInformation("Upbit WebSocket 연결 완료. 코인 수: {Count}", marketCodes.Count);
+        logger.LogInformation("Upbit WebSocket 연결 완료. 코인 수: {0}, 마켓 수: {1}", marketCodes.Count, uniqueQuotes);
 
         // 구독 메시지 전송
         var subscribeMsg = BuildSubscribeMessage(marketCodes);
@@ -142,12 +145,42 @@ public sealed class UpbitWebSocketService(
 
             if (type == "ticker")
             {
-                ProcessTickerMessage(root, tickers);
+                await ProcessTickerMessageAsync(root, tickers, ct);
 
-                // 전체 Ticker 목록을 Redis 저장
+                // 전체 Ticker 목록을 quote별로 Redis 저장 + SignalR 전송
                 if (tickers.Count > 0)
                 {
-                    await cache.SetAsync(CacheKeys.CryptoTickers, tickers.Values.ToList(), ct);
+                    var grouped = tickers.Values.GroupBy(t => t.Market.Split('-')[0]);
+
+                    foreach (var group in grouped)
+                    {
+                        await cache.SetAsync(CacheKeys.CryptoTickers(group.Key), group.ToList(), ct);
+                    }
+
+                    try
+                    {
+                        foreach (var group in grouped)
+                        {
+                            var hubTickers = group.Select(t => new CryptoHubData.TickerData(
+                                t.Market, t.Market.Split('-')[^1],
+                                t.OpeningPrice, t.HighPrice, t.LowPrice, t.TradePrice,
+                                t.PrevClosingPrice, t.Change, t.ChangePrice, t.SignedChangePrice,
+                                t.ChangeRate, t.SignedChangeRate, t.TradeVolume, t.AccTradeVolume,
+                                t.AccTradeVolume24h, t.AccTradePrice, t.AccTradePrice24h,
+                                t.TradeDate, t.TradeTime, t.TradeTimestamp, t.AskBid,
+                                t.AccAskVolume, t.AccBidVolume, t.Highest52WeekPrice,
+                                t.Highest52WeekDate, t.Lowest52WeekPrice, t.Lowest52WeekDate,
+                                t.MarketState, t.DelistingDate, t.MarketWarning,
+                                t.Timestamp, t.StreamType
+                            )).ToList();
+
+                            await hub.SendTickersAsync(hubTickers, ct);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        logger.LogDebug(ex, "SignalR tickers 전송 실패");
+                    }
                 }
             }
             else if (type == "trade")
@@ -169,16 +202,14 @@ public sealed class UpbitWebSocketService(
         }
     }
 
-    // DB에서 활성화된 코인 심볼 목록을 가져와서 "KRW-심볼" 형식의 마켓 코드 리스트로 반환
+    // DB에서 활성 코인의 전체 마켓 코드를 CoinMarket 테이블에서 조회
     private async Task<List<string>> GetMarketCodesAsync(CancellationToken ct)
     {
         using var scope = scopeFactory.CreateScope();
 
         var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
 
-        var symbols = await db.Coin.AsNoTracking().Where(c => c.IsActive && !c.IsDelisted).Select(c => c.Symbol).ToListAsync(ct);
-
-        return [..symbols.Select(s => $"KRW-{s.ToUpper()}")];
+        return await db.CoinMarket.AsNoTracking().Join(db.Coin.AsNoTracking().Where(c => c.IsActive && !c.IsDelisted), cm => cm.CoinID, c => c.ID, (cm, c) => cm.Market).ToListAsync(ct);
     }
 
     // 구독 메시지 생성 (Ticker + Trade + Orderbook + Candle.1m)
@@ -197,7 +228,7 @@ public sealed class UpbitWebSocketService(
 
     // ─── Ticker ─────────────────────────────────────────────────────
 
-    private void ProcessTickerMessage(JsonElement root, Dictionary<string, UpbitTicker> tickers)
+    private async Task ProcessTickerMessageAsync(JsonElement root, Dictionary<string, UpbitTicker> tickers, CancellationToken ct)
     {
         var market = root.TryGetProperty("cd", out var cd) ? cd.GetString() ?? "" : "";
         if (string.IsNullOrEmpty(market))
@@ -205,22 +236,69 @@ public sealed class UpbitWebSocketService(
             return;
         }
 
-        var tradePrice = root.TryGetProperty("tp", out var tp) ? tp.GetDecimal() : 0m;
-        var change = root.TryGetProperty("c", out var c) ? c.GetString() ?? "" : "";
-        var signedChangePrice = root.TryGetProperty("scp", out var scp) ? scp.GetDecimal() : 0m;
-        var signedChangeRate = root.TryGetProperty("scr", out var scr) ? scr.GetDecimal() : 0m;
-        var accTradePrice24h = root.TryGetProperty("atp24h", out var atp) ? atp.GetDecimal() : 0m;
+        var ticker = new UpbitTicker(
+            market,
+            root.TryGetProperty("op", out var op) ? op.GetDecimal() : 0m,
+            root.TryGetProperty("hp", out var hp) ? hp.GetDecimal() : 0m,
+            root.TryGetProperty("lp", out var lp) ? lp.GetDecimal() : 0m,
+            root.TryGetProperty("tp", out var tp) ? tp.GetDecimal() : 0m,
+            root.TryGetProperty("pcp", out var pcp) ? pcp.GetDecimal() : 0m,
+            GetSafeString(root, "c"),
+            root.TryGetProperty("cp", out var cpVal) ? cpVal.GetDecimal() : 0m,
+            root.TryGetProperty("scp", out var scp) ? scp.GetDecimal() : 0m,
+            root.TryGetProperty("cr", out var cr) ? cr.GetDecimal() : 0m,
+            root.TryGetProperty("scr", out var scr) ? scr.GetDecimal() : 0m,
+            root.TryGetProperty("tv", out var tv) ? tv.GetDecimal() : 0m,
+            root.TryGetProperty("atv", out var atv) ? atv.GetDecimal() : 0m,
+            root.TryGetProperty("atv24h", out var atv24h) ? atv24h.GetDecimal() : 0m,
+            root.TryGetProperty("atp", out var atp) ? atp.GetDecimal() : 0m,
+            root.TryGetProperty("atp24h", out var atp24h) ? atp24h.GetDecimal() : 0m,
+            GetSafeString(root, "tdt"),
+            GetSafeString(root, "ttm"),
+            root.TryGetProperty("ttms", out var ttms) ? ttms.GetInt64() : 0L,
+            GetSafeString(root, "ab"),
+            root.TryGetProperty("aav", out var aav) ? aav.GetDecimal() : 0m,
+            root.TryGetProperty("abv", out var abv) ? abv.GetDecimal() : 0m,
+            root.TryGetProperty("h52wp", out var h52wp) ? h52wp.GetDecimal() : 0m,
+            GetSafeString(root, "h52wdt"),
+            root.TryGetProperty("l52wp", out var l52wp) ? l52wp.GetDecimal() : 0m,
+            GetSafeString(root, "l52wdt"),
+            GetSafeString(root, "ms"),
+            GetSafeNullableString(root, "dd"),
+            GetSafeString(root, "mw"),
+            root.TryGetProperty("tms", out var tms) ? tms.GetInt64() : 0L,
+            GetSafeString(root, "st")
+        );
 
-        var ticker = new UpbitTicker(market, tradePrice, change, signedChangePrice, signedChangeRate, accTradePrice24h);
         tickers[market] = ticker;
 
-        // 개별 Ticker도 Redis에 저장
-        var symbol = market.Replace("KRW-", "");
-        _ = cache.SetAsync(CacheKeys.CryptoTicker(symbol), ticker);
+        // 개별 Ticker Redis 저장 + SignalR 전송
+        var symbol = market.Split('-')[^1];
+        await cache.SetAsync(CacheKeys.CryptoTicker(market), ticker, ct);
+
+        try
+        {
+            await hub.SendTickerAsync(new CryptoHubData.TickerData(
+                market, symbol, ticker.OpeningPrice, ticker.HighPrice, ticker.LowPrice,
+                ticker.TradePrice, ticker.PrevClosingPrice, ticker.Change,
+                ticker.ChangePrice, ticker.SignedChangePrice, ticker.ChangeRate,
+                ticker.SignedChangeRate, ticker.TradeVolume, ticker.AccTradeVolume,
+                ticker.AccTradeVolume24h, ticker.AccTradePrice, ticker.AccTradePrice24h,
+                ticker.TradeDate, ticker.TradeTime, ticker.TradeTimestamp, ticker.AskBid,
+                ticker.AccAskVolume, ticker.AccBidVolume, ticker.Highest52WeekPrice,
+                ticker.Highest52WeekDate, ticker.Lowest52WeekPrice, ticker.Lowest52WeekDate,
+                ticker.MarketState, ticker.DelistingDate, ticker.MarketWarning,
+                ticker.Timestamp, ticker.StreamType
+            ), ct);
+        }
+        catch (Exception ex)
+        {
+            logger.LogDebug(ex, "SignalR ticker 전송 실패: {Market}", market);
+        }
     }
 
     // ─── Trade ──────────────────────────────────────────────────────
-    // SIMPLE 필드: cd, tp, tv, ab, pcp, td, ttm, ttms, sid, tms, st
+    // SIMPLE 필드: cd, tp, tv, ab, pcp, c, cp, td, ttm, ttms, sid, tms, st, bap, bas, bbp, bbs
 
     private async Task ProcessTradeMessageAsync(JsonElement root, CancellationToken ct)
     {
@@ -230,19 +308,47 @@ public sealed class UpbitWebSocketService(
             return;
         }
 
-        var symbol = market.Replace("KRW-", "");
-        var tradePrice = root.TryGetProperty("tp", out var tp) ? tp.GetDecimal() : 0m;
-        var tradeVolume = root.TryGetProperty("tv", out var tv) ? tv.GetDecimal() : 0m;
-        var askBid = root.TryGetProperty("ab", out var ab) ? ab.GetString() ?? "" : "";
-        var timestamp = root.TryGetProperty("ttms", out var ttms) ? ttms.GetInt64() : 0L;
-
-        var trade = new LiveTrade(timestamp, tradePrice, tradeVolume, askBid);
+        var symbol = market.Split('-')[^1];
+
+        var trade = new LiveTrade(
+            root.TryGetProperty("tp", out var tp) ? tp.GetDecimal() : 0m,
+            root.TryGetProperty("tv", out var tv) ? tv.GetDecimal() : 0m,
+            root.TryGetProperty("ab", out var ab) ? ab.GetString() ?? "" : "",
+            root.TryGetProperty("pcp", out var pcp) ? pcp.GetDecimal() : 0m,
+            root.TryGetProperty("c", out var c) ? c.GetString() ?? "" : "",
+            root.TryGetProperty("cp", out var cp) ? cp.GetDecimal() : 0m,
+            root.TryGetProperty("td", out var td) ? td.GetString() ?? "" : "",
+            root.TryGetProperty("ttm", out var ttm) ? ttm.GetString() ?? "" : "",
+            root.TryGetProperty("ttms", out var ttms) ? ttms.GetInt64() : 0L,
+            root.TryGetProperty("sid", out var sid) ? sid.GetInt64() : 0L,
+            root.TryGetProperty("tms", out var tms) ? tms.GetInt64() : 0L,
+            root.TryGetProperty("st", out var st) ? st.GetString() ?? "" : "",
+            root.TryGetProperty("bap", out var bap) ? bap.GetDecimal() : 0m,
+            root.TryGetProperty("bas", out var bas) ? bas.GetDecimal() : 0m,
+            root.TryGetProperty("bbp", out var bbp) ? bbp.GetDecimal() : 0m,
+            root.TryGetProperty("bbs", out var bbs) ? bbs.GetDecimal() : 0m
+        );
+
+        await cache.SetAsync(CacheKeys.CryptoTradeLive(market), trade, ct);
 
-        await cache.SetAsync(CacheKeys.CryptoTradeLive(symbol), trade, ct);
+        try
+        {
+            await hub.SendTradeAsync(new CryptoHubData.TradeData(
+                market, symbol, trade.TradePrice, trade.TradeVolume, trade.AskBid,
+                trade.PrevClosingPrice, trade.Change, trade.ChangePrice,
+                trade.TradeDate, trade.TradeTime, trade.TradeTimestamp,
+                trade.SequentialId, trade.Timestamp, trade.StreamType,
+                trade.BestAskPrice, trade.BestAskSize, trade.BestBidPrice, trade.BestBidSize
+            ), ct);
+        }
+        catch (Exception ex)
+        {
+            logger.LogDebug(ex, "SignalR trade 전송 실패: {Market}", market);
+        }
     }
 
     // ─── Orderbook ──────────────────────────────────────────────────
-    // SIMPLE 필드: cd, tas, tbs, obu[{ap, bp, as, bs}], tms, st
+    // SIMPLE 필드: cd, tas, tbs, obu[{ap, bp, as, bs}], tms, lv, st
 
     private async Task ProcessOrderbookMessageAsync(JsonElement root, CancellationToken ct)
     {
@@ -252,10 +358,12 @@ public sealed class UpbitWebSocketService(
             return;
         }
 
-        var symbol = market.Replace("KRW-", "");
+        var symbol = market.Split('-')[^1];
         var totalAskSize = root.TryGetProperty("tas", out var tas) ? tas.GetDecimal() : 0m;
         var totalBidSize = root.TryGetProperty("tbs", out var tbs) ? tbs.GetDecimal() : 0m;
         var timestamp = root.TryGetProperty("tms", out var tms) ? tms.GetInt64() : 0L;
+        var level = root.TryGetProperty("lv", out var lv) ? lv.GetDecimal() : 0m;
+        var streamType = root.TryGetProperty("st", out var st) ? st.GetString() ?? "" : "";
 
         var units = new List<LiveOrderbookUnit>();
         if (root.TryGetProperty("obu", out var obu) && obu.ValueKind == JsonValueKind.Array)
@@ -271,13 +379,26 @@ public sealed class UpbitWebSocketService(
             }
         }
 
-        var orderbook = new LiveOrderbook(totalAskSize, totalBidSize, units, timestamp);
+        var orderbook = new LiveOrderbook(totalAskSize, totalBidSize, units, timestamp, level, streamType);
 
-        await cache.SetAsync(CacheKeys.CryptoOrderbookLive(symbol), orderbook, ct);
+        await cache.SetAsync(CacheKeys.CryptoOrderbookLive(market), orderbook, ct);
+
+        try
+        {
+            await hub.SendOrderbookAsync(new CryptoHubData.OrderbookData(
+                market, symbol, orderbook.TotalAskSize, orderbook.TotalBidSize,
+                [..orderbook.Units.Select(u => new CryptoHubData.OrderbookUnitData(u.AskPrice, u.BidPrice, u.AskSize, u.BidSize))],
+                orderbook.Timestamp, orderbook.Level, orderbook.StreamType
+            ), ct);
+        }
+        catch (Exception ex)
+        {
+            logger.LogDebug(ex, "SignalR orderbook 전송 실패: {Market}", market);
+        }
     }
 
     // ─── Candle ─────────────────────────────────────────────────────
-    // SIMPLE 필드: cd, op, hp, lp, tp, catv, catp, tms, st
+    // SIMPLE 필드: cd, cdttmu, cdttmk, op, hp, lp, tp, catv, catp, tms, st
 
     private async Task ProcessCandleMessageAsync(JsonElement root, CancellationToken ct)
     {
@@ -287,21 +408,36 @@ public sealed class UpbitWebSocketService(
             return;
         }
 
-        var symbol = market.Replace("KRW-", "");
-        var open = root.TryGetProperty("op", out var op) ? op.GetDecimal() : 0m;
-        var high = root.TryGetProperty("hp", out var hp) ? hp.GetDecimal() : 0m;
-        var low = root.TryGetProperty("lp", out var lp) ? lp.GetDecimal() : 0m;
-        var close = root.TryGetProperty("tp", out var tp) ? tp.GetDecimal() : 0m;
-        var volume = root.TryGetProperty("catv", out var catv) ? catv.GetDecimal() : 0m;
-        var tradePrice = root.TryGetProperty("catp", out var catp) ? catp.GetDecimal() : 0m;
-        var timestamp = root.TryGetProperty("tms", out var tms) ? tms.GetInt64() : 0L;
+        var symbol = market.Split('-')[^1];
 
-        // tms는 ms 단위 → 초 단위로 변환 (캔들 Time 필드와 일관성)
-        var timeSec = timestamp / 1000;
+        var candle = new LiveCandle(
+            root.TryGetProperty("cdttmu", out var cdttmu) ? cdttmu.GetString() ?? "" : "",
+            root.TryGetProperty("cdttmk", out var cdttmk) ? cdttmk.GetString() ?? "" : "",
+            root.TryGetProperty("op", out var op) ? op.GetDecimal() : 0m,
+            root.TryGetProperty("hp", out var hp) ? hp.GetDecimal() : 0m,
+            root.TryGetProperty("lp", out var lp) ? lp.GetDecimal() : 0m,
+            root.TryGetProperty("tp", out var tp) ? tp.GetDecimal() : 0m,
+            root.TryGetProperty("catv", out var catv) ? catv.GetDecimal() : 0m,
+            root.TryGetProperty("catp", out var catp) ? catp.GetDecimal() : 0m,
+            root.TryGetProperty("tms", out var tms) ? tms.GetInt64() : 0L,
+            root.TryGetProperty("st", out var st) ? st.GetString() ?? "" : ""
+        );
 
-        var candle = new LiveCandle(timeSec, open, high, low, close, volume, tradePrice);
+        await cache.SetAsync(CacheKeys.CryptoCandleLive(market, "m1"), candle, ct);
 
-        await cache.SetAsync(CacheKeys.CryptoCandleLive(symbol, "m1"), candle, ct);
+        try
+        {
+            await hub.SendCandleAsync(new CryptoHubData.CandleData(
+                market, symbol, candle.CandleDateTimeUtc, candle.CandleDateTimeKst,
+                candle.OpeningPrice, candle.HighPrice, candle.LowPrice, candle.TradePrice,
+                candle.CandleAccTradeVolume, candle.CandleAccTradePrice,
+                candle.Timestamp, candle.StreamType
+            ), ct);
+        }
+        catch (Exception ex)
+        {
+            logger.LogDebug(ex, "SignalR candle 전송 실패: {Market}", market);
+        }
     }
 
     // ─── PING ───────────────────────────────────────────────────────
@@ -327,20 +463,56 @@ public sealed class UpbitWebSocketService(
         }
     }
 
+    // ─── JSON 안전 파싱 헬퍼 ────────────────────────────────────────
+
+    private static string GetSafeString(JsonElement root, string prop, string fallback = "")
+    {
+        if (!root.TryGetProperty(prop, out var el))
+        {
+            return fallback;
+        }
+
+        return el.ValueKind == JsonValueKind.String ? el.GetString() ?? fallback : fallback;
+    }
+
+    private static string? GetSafeNullableString(JsonElement root, string prop)
+    {
+        if (!root.TryGetProperty(prop, out var el))
+        {
+            return null;
+        }
+
+        return el.ValueKind == JsonValueKind.String ? el.GetString() : el.ToString();
+    }
+
     // ─── Live 데이터 Redis 저장용 Records ──────────────────────────
 
     internal sealed record LiveTrade(
-        long Timestamp,
         decimal TradePrice,
         decimal TradeVolume,
-        string AskBid
+        string AskBid,
+        decimal PrevClosingPrice,
+        string Change,
+        decimal ChangePrice,
+        string TradeDate,
+        string TradeTime,
+        long TradeTimestamp,
+        long SequentialId,
+        long Timestamp,
+        string StreamType,
+        decimal BestAskPrice,
+        decimal BestAskSize,
+        decimal BestBidPrice,
+        decimal BestBidSize
     );
 
     internal sealed record LiveOrderbook(
         decimal TotalAskSize,
         decimal TotalBidSize,
         List<LiveOrderbookUnit> Units,
-        long Timestamp
+        long Timestamp,
+        decimal Level,
+        string StreamType
     );
 
     internal sealed record LiveOrderbookUnit(
@@ -351,12 +523,15 @@ public sealed class UpbitWebSocketService(
     );
 
     internal sealed record LiveCandle(
-        long Time,
-        decimal Open,
-        decimal High,
-        decimal Low,
-        decimal Close,
-        decimal Volume,
-        decimal TradePrice
+        string CandleDateTimeUtc,
+        string CandleDateTimeKst,
+        decimal OpeningPrice,
+        decimal HighPrice,
+        decimal LowPrice,
+        decimal TradePrice,
+        decimal CandleAccTradeVolume,
+        decimal CandleAccTradePrice,
+        long Timestamp,
+        string StreamType
     );
 }

+ 1 - 1
Infrastructure/DependencyInjection.cs

@@ -5,6 +5,7 @@ using Application.Abstractions.Data;
 using Application.Abstractions.Identity;
 using Application.Abstractions.Messaging.Email;
 using Application.Abstractions.Cache;
+using Application.Abstractions.Crypto;
 using Infrastructure.Authentication;
 using Infrastructure.Messaging.Email;
 using Infrastructure.Persistence;
@@ -12,7 +13,6 @@ using Infrastructure.Persistence.Identity;
 using Infrastructure.Storage;
 using Infrastructure.Cache;
 using Infrastructure.Crypto;
-using Application.Abstractions.Crypto;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Identity;

+ 0 - 4
Infrastructure/Infrastructure.csproj

@@ -6,10 +6,6 @@
     <Nullable>enable</Nullable>
   </PropertyGroup>
 
-  <ItemGroup>
-    <FrameworkReference Include="Microsoft.AspNetCore.App" />
-  </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="AspNetCore.HealthChecks.Redis" Version="9.0.0" />
     <PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="9.0.0" />

+ 1 - 0
Infrastructure/Persistence/AppDbContext.cs

@@ -29,6 +29,7 @@ namespace Infrastructure.Persistence
         public DbSet<CoinCategory> CoinCategory { get; set; }
         public DbSet<Coin> Coin { get; set; }
         public DbSet<CoinCategoryMap> CoinCategoryMap { get; set; }
+        public DbSet<CoinMarket> CoinMarket { get; set; }
 
         // Config
         public DbSet<Config> Config { get; set; }

+ 1 - 0
Infrastructure/Persistence/Configurations/Crypto/CoinConfiguration.cs

@@ -16,6 +16,7 @@ public sealed class CoinConfiguration : IEntityTypeConfiguration<Coin>
         builder.HasIndex(x => new { x.Symbol, x.IsActive });
 
         builder.HasMany(x => x.CoinCategoryMap).WithOne(x => x.Coin).HasForeignKey(x => x.CoinID).OnDelete(DeleteBehavior.Cascade);
+        builder.HasMany(x => x.CoinMarket).WithOne(x => x.Coin).HasForeignKey(x => x.CoinID).OnDelete(DeleteBehavior.Cascade);
 
         builder.ToTable(nameof(Coin), t => t.HasComment("코인/토큰"));
         builder.HasKey(x => x.ID);

+ 20 - 0
Infrastructure/Persistence/Configurations/Crypto/CoinMarketConfiguration.cs

@@ -0,0 +1,20 @@
+using Domain.Entities.Crypto;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Infrastructure.Persistence.Configurations.Crypto;
+
+public sealed class CoinMarketConfiguration : IEntityTypeConfiguration<CoinMarket>
+{
+    public void Configure(EntityTypeBuilder<CoinMarket> builder)
+    {
+        builder.HasIndex(x => new { x.CoinID, x.Market }).IsUnique();
+        builder.HasIndex(x => x.Market);
+
+        builder.ToTable(nameof(CoinMarket), t => t.HasComment("코인-거래쌍 연결"));
+        builder.HasKey(x => x.ID);
+        builder.Property(x => x.ID).ValueGeneratedOnAdd().HasComment("PK");
+        builder.Property(x => x.CoinID).IsRequired().HasComment("코인 ID");
+        builder.Property(x => x.Market).HasMaxLength(30).IsRequired().HasComment("거래쌍 (KRW-BTC 등)");
+    }
+}

+ 6140 - 0
Infrastructure/Persistence/Migrations/20260219090105_AddCoinMarket.Designer.cs

@@ -0,0 +1,6140 @@
+// <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.Migrations.AppDb
+{
+    [DbContext(typeof(AppDbContext))]
+    [Migration("20260219090105_AddCoinMarket")]
+    partial class AddCoinMarket
+    {
+        /// <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.Crypto.Coin", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("ContractAddress")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("컨트랙트 주소");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("설명");
+
+                    b.Property<short>("DisplayOrder")
+                        .HasColumnType("smallint")
+                        .HasComment("메인 노출 순서");
+
+                    b.Property<string>("EngName")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("영문 이름");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<bool>("IsDelisted")
+                        .HasColumnType("bit")
+                        .HasComment("상장 폐지");
+
+                    b.Property<bool>("IsFeatured")
+                        .HasColumnType("bit")
+                        .HasComment("주요 코인 (메인 노출)");
+
+                    b.Property<bool>("IsNew")
+                        .HasColumnType("bit")
+                        .HasComment("신규 상장");
+
+                    b.Property<bool>("IsWarning")
+                        .HasColumnType("bit")
+                        .HasComment("위험 경고");
+
+                    b.Property<string>("KorName")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("한글 이름");
+
+                    b.Property<string>("LogoImage")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("로고 이미지");
+
+                    b.Property<string>("Market")
+                        .IsRequired()
+                        .ValueGeneratedOnAdd()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasDefaultValue("")
+                        .HasComment("거래쌍 (KRW-BTC 등)");
+
+                    b.Property<string>("Symbol")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("심볼 (BTC, ETH 등)");
+
+                    b.Property<string>("TelegramUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("텔레그램 URL");
+
+                    b.Property<string>("TwitterUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("트위터 URL");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("WebsiteUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("홈페이지 URL");
+
+                    b.Property<string>("WhitepaperUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("백서 URL");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("DisplayOrder")
+                        .HasDatabaseName("IX_Coin_DisplayOrder");
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("IsDelisted");
+
+                    b.HasIndex("IsFeatured")
+                        .HasDatabaseName("IX_Coin_IsFeatured");
+
+                    b.HasIndex("IsNew");
+
+                    b.HasIndex("IsWarning");
+
+                    b.HasIndex("Symbol")
+                        .IsUnique();
+
+                    b.HasIndex("Symbol", "IsActive");
+
+                    b.ToTable("Coin", null, t =>
+                        {
+                            t.HasComment("코인/토큰");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinCategory", 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>("Name")
+                        .IsRequired()
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("카테고리 이름");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("Order");
+
+                    b.ToTable("CoinCategory", null, t =>
+                        {
+                            t.HasComment("코인 카테고리");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinCategoryMap", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CategoryID")
+                        .HasColumnType("int")
+                        .HasComment("카테고리 ID");
+
+                    b.Property<int>("CoinID")
+                        .HasColumnType("int")
+                        .HasComment("코인 ID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CategoryID");
+
+                    b.HasIndex("CoinID", "CategoryID")
+                        .IsUnique();
+
+                    b.ToTable("CoinCategoryMap", 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?>("CoinID")
+                        .HasColumnType("int");
+
+                    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("CoinID");
+
+                    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<string>("PasswordHash")
+                        .HasColumnType("nvarchar(max)");
+
+                    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.Members.RefreshToken", b =>
+                {
+                    b.Property<long>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime>("ExpiresAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<bool>("IsRevoked")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bit")
+                        .HasDefaultValue(false);
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime?>("RevokedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("Token")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("nvarchar(256)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("Token")
+                        .IsUnique();
+
+                    b.ToTable("RefreshToken", 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.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<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("Order");
+
+                    b.HasIndex("PositionID");
+
+                    b.HasIndex("Order", "IsActive");
+
+                    b.HasIndex("StartAt", "EndAt", "Order", "IsActive");
+
+                    b.ToTable("Popup", null, t =>
+                        {
+                            t.HasComment("팝업");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Popup.PopupPosition", 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("PopupPosition", 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.CryptoConfig", "Crypto", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<int>("MainPageCoinCount")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("int")
+                                .HasDefaultValue(10)
+                                .HasColumnName("Crypto_MainPageCoinCount")
+                                .HasComment("메인 페이지 기본 표시 코인 수");
+
+                            b1.Property<decimal>("PlungeThreshold")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("decimal(5,2)")
+                                .HasDefaultValue(-5.0m)
+                                .HasColumnName("Crypto_PlungeThreshold")
+                                .HasComment("급락 임계값 (%)");
+
+                            b1.Property<decimal>("SurgeThreshold")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("decimal(5,2)")
+                                .HasDefaultValue(5.0m)
+                                .HasColumnName("Crypto_SurgeThreshold")
+                                .HasComment("급등 임계값 (%)");
+
+                            b1.Property<int>("TickerRefreshSeconds")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("int")
+                                .HasDefaultValue(5)
+                                .HasColumnName("Crypto_TickerRefreshSeconds")
+                                .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("Crypto")
+                        .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.Crypto.CoinCategoryMap", b =>
+                {
+                    b.HasOne("Domain.Entities.Crypto.CoinCategory", "CoinCategory")
+                        .WithMany("CoinCategoryMap")
+                        .HasForeignKey("CategoryID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Crypto.Coin", "Coin")
+                        .WithMany("CoinCategoryMap")
+                        .HasForeignKey("CoinID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Coin");
+
+                    b.Navigation("CoinCategory");
+                });
+
+            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.HasOne("Domain.Entities.Crypto.Coin", "Coin")
+                        .WithMany()
+                        .HasForeignKey("CoinID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.Navigation("BoardGroup");
+
+                    b.Navigation("Coin");
+                });
+
+            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.Members.RefreshToken", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("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.Page.Popup.Popup", b =>
+                {
+                    b.HasOne("Domain.Entities.Page.Popup.PopupPosition", "PopupPosition")
+                        .WithMany("Popups")
+                        .HasForeignKey("PositionID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("PopupPosition");
+                });
+
+            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.Crypto.Coin", b =>
+                {
+                    b.Navigation("CoinCategoryMap");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinCategory", b =>
+                {
+                    b.Navigation("CoinCategoryMap");
+                });
+
+            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.Page.Popup.PopupPosition", b =>
+                {
+                    b.Navigation("Popups");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.Wallet", b =>
+                {
+                    b.Navigation("Balances");
+
+                    b.Navigation("Transactions");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 31 - 0
Infrastructure/Persistence/Migrations/20260219090105_AddCoinMarket.cs

@@ -0,0 +1,31 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations.AppDb
+{
+    /// <inheritdoc />
+    public partial class AddCoinMarket : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<string>(
+                name: "Market",
+                table: "Coin",
+                type: "nvarchar(30)",
+                maxLength: 30,
+                nullable: false,
+                defaultValue: "",
+                comment: "거래쌍 (KRW-BTC 등)");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "Market",
+                table: "Coin");
+        }
+    }
+}

+ 6177 - 0
Infrastructure/Persistence/Migrations/20260219091041_AddCoinMarketTable.Designer.cs

@@ -0,0 +1,6177 @@
+// <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.Migrations.AppDb
+{
+    [DbContext(typeof(AppDbContext))]
+    [Migration("20260219091041_AddCoinMarketTable")]
+    partial class AddCoinMarketTable
+    {
+        /// <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.Crypto.Coin", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<string>("ContractAddress")
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("컨트랙트 주소");
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("등록 일시");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("nvarchar(max)")
+                        .HasComment("설명");
+
+                    b.Property<short>("DisplayOrder")
+                        .HasColumnType("smallint")
+                        .HasComment("메인 노출 순서");
+
+                    b.Property<string>("EngName")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("영문 이름");
+
+                    b.Property<bool>("IsActive")
+                        .HasColumnType("bit")
+                        .HasComment("사용 여부");
+
+                    b.Property<bool>("IsDelisted")
+                        .HasColumnType("bit")
+                        .HasComment("상장 폐지");
+
+                    b.Property<bool>("IsFeatured")
+                        .HasColumnType("bit")
+                        .HasComment("주요 코인 (메인 노출)");
+
+                    b.Property<bool>("IsNew")
+                        .HasColumnType("bit")
+                        .HasComment("신규 상장");
+
+                    b.Property<bool>("IsWarning")
+                        .HasColumnType("bit")
+                        .HasComment("위험 경고");
+
+                    b.Property<string>("KorName")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("nvarchar(200)")
+                        .HasComment("한글 이름");
+
+                    b.Property<string>("LogoImage")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("로고 이미지");
+
+                    b.Property<string>("Symbol")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("심볼 (BTC, ETH 등)");
+
+                    b.Property<string>("TelegramUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("텔레그램 URL");
+
+                    b.Property<string>("TwitterUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("트위터 URL");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.Property<string>("WebsiteUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("홈페이지 URL");
+
+                    b.Property<string>("WhitepaperUrl")
+                        .HasMaxLength(500)
+                        .HasColumnType("nvarchar(500)")
+                        .HasComment("백서 URL");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("DisplayOrder")
+                        .HasDatabaseName("IX_Coin_DisplayOrder");
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("IsDelisted");
+
+                    b.HasIndex("IsFeatured")
+                        .HasDatabaseName("IX_Coin_IsFeatured");
+
+                    b.HasIndex("IsNew");
+
+                    b.HasIndex("IsWarning");
+
+                    b.HasIndex("Symbol")
+                        .IsUnique();
+
+                    b.HasIndex("Symbol", "IsActive");
+
+                    b.ToTable("Coin", null, t =>
+                        {
+                            t.HasComment("코인/토큰");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinCategory", 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>("Name")
+                        .IsRequired()
+                        .HasMaxLength(100)
+                        .HasColumnType("nvarchar(100)")
+                        .HasComment("카테고리 이름");
+
+                    b.Property<short>("Order")
+                        .HasColumnType("smallint")
+                        .HasComment("순서");
+
+                    b.Property<DateTime?>("UpdatedAt")
+                        .HasColumnType("datetime2")
+                        .HasComment("수정 일시");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Code")
+                        .IsUnique();
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("Order");
+
+                    b.ToTable("CoinCategory", null, t =>
+                        {
+                            t.HasComment("코인 카테고리");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinCategoryMap", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CategoryID")
+                        .HasColumnType("int")
+                        .HasComment("카테고리 ID");
+
+                    b.Property<int>("CoinID")
+                        .HasColumnType("int")
+                        .HasComment("코인 ID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("CategoryID");
+
+                    b.HasIndex("CoinID", "CategoryID")
+                        .IsUnique();
+
+                    b.ToTable("CoinCategoryMap", null, t =>
+                        {
+                            t.HasComment("코인-카테고리 연결");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinMarket", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CoinID")
+                        .HasColumnType("int")
+                        .HasComment("코인 ID");
+
+                    b.Property<string>("Market")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("거래쌍 (KRW-BTC 등)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Market");
+
+                    b.HasIndex("CoinID", "Market")
+                        .IsUnique();
+
+                    b.ToTable("CoinMarket", 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?>("CoinID")
+                        .HasColumnType("int");
+
+                    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("CoinID");
+
+                    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<string>("PasswordHash")
+                        .HasColumnType("nvarchar(max)");
+
+                    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.Members.RefreshToken", b =>
+                {
+                    b.Property<long>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("ID"));
+
+                    b.Property<DateTime>("CreatedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<DateTime>("ExpiresAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<bool>("IsRevoked")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bit")
+                        .HasDefaultValue(false);
+
+                    b.Property<int>("MemberID")
+                        .HasColumnType("int");
+
+                    b.Property<DateTime?>("RevokedAt")
+                        .HasColumnType("datetime2");
+
+                    b.Property<string>("Token")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("nvarchar(256)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("MemberID");
+
+                    b.HasIndex("Token")
+                        .IsUnique();
+
+                    b.ToTable("RefreshToken", 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.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<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("Order");
+
+                    b.HasIndex("PositionID");
+
+                    b.HasIndex("Order", "IsActive");
+
+                    b.HasIndex("StartAt", "EndAt", "Order", "IsActive");
+
+                    b.ToTable("Popup", null, t =>
+                        {
+                            t.HasComment("팝업");
+                        });
+                });
+
+            modelBuilder.Entity("Domain.Entities.Page.Popup.PopupPosition", 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("PopupPosition", 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.CryptoConfig", "Crypto", b1 =>
+                        {
+                            b1.Property<int>("ConfigID")
+                                .HasColumnType("int");
+
+                            b1.Property<int>("MainPageCoinCount")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("int")
+                                .HasDefaultValue(10)
+                                .HasColumnName("Crypto_MainPageCoinCount")
+                                .HasComment("메인 페이지 기본 표시 코인 수");
+
+                            b1.Property<decimal>("PlungeThreshold")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("decimal(5,2)")
+                                .HasDefaultValue(-5.0m)
+                                .HasColumnName("Crypto_PlungeThreshold")
+                                .HasComment("급락 임계값 (%)");
+
+                            b1.Property<decimal>("SurgeThreshold")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("decimal(5,2)")
+                                .HasDefaultValue(5.0m)
+                                .HasColumnName("Crypto_SurgeThreshold")
+                                .HasComment("급등 임계값 (%)");
+
+                            b1.Property<int>("TickerRefreshSeconds")
+                                .ValueGeneratedOnAdd()
+                                .HasColumnType("int")
+                                .HasDefaultValue(5)
+                                .HasColumnName("Crypto_TickerRefreshSeconds")
+                                .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("Crypto")
+                        .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.Crypto.CoinCategoryMap", b =>
+                {
+                    b.HasOne("Domain.Entities.Crypto.CoinCategory", "CoinCategory")
+                        .WithMany("CoinCategoryMap")
+                        .HasForeignKey("CategoryID")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.HasOne("Domain.Entities.Crypto.Coin", "Coin")
+                        .WithMany("CoinCategoryMap")
+                        .HasForeignKey("CoinID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Coin");
+
+                    b.Navigation("CoinCategory");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinMarket", b =>
+                {
+                    b.HasOne("Domain.Entities.Crypto.Coin", "Coin")
+                        .WithMany("CoinMarket")
+                        .HasForeignKey("CoinID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Coin");
+                });
+
+            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.HasOne("Domain.Entities.Crypto.Coin", "Coin")
+                        .WithMany()
+                        .HasForeignKey("CoinID")
+                        .OnDelete(DeleteBehavior.SetNull);
+
+                    b.Navigation("BoardGroup");
+
+                    b.Navigation("Coin");
+                });
+
+            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.Members.RefreshToken", b =>
+                {
+                    b.HasOne("Domain.Entities.Members.Member", "Member")
+                        .WithMany()
+                        .HasForeignKey("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.Page.Popup.Popup", b =>
+                {
+                    b.HasOne("Domain.Entities.Page.Popup.PopupPosition", "PopupPosition")
+                        .WithMany("Popups")
+                        .HasForeignKey("PositionID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("PopupPosition");
+                });
+
+            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.Crypto.Coin", b =>
+                {
+                    b.Navigation("CoinCategoryMap");
+
+                    b.Navigation("CoinMarket");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinCategory", b =>
+                {
+                    b.Navigation("CoinCategoryMap");
+                });
+
+            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.Page.Popup.PopupPosition", b =>
+                {
+                    b.Navigation("Popups");
+                });
+
+            modelBuilder.Entity("Domain.Entities.Wallets.Wallet", b =>
+                {
+                    b.Navigation("Balances");
+
+                    b.Navigation("Transactions");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}

+ 66 - 0
Infrastructure/Persistence/Migrations/20260219091041_AddCoinMarketTable.cs

@@ -0,0 +1,66 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Infrastructure.Migrations.AppDb
+{
+    /// <inheritdoc />
+    public partial class AddCoinMarketTable : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "Market",
+                table: "Coin");
+
+            migrationBuilder.CreateTable(
+                name: "CoinMarket",
+                columns: table => new
+                {
+                    ID = table.Column<int>(type: "int", nullable: false, comment: "PK")
+                        .Annotation("SqlServer:Identity", "1, 1"),
+                    CoinID = table.Column<int>(type: "int", nullable: false, comment: "코인 ID"),
+                    Market = table.Column<string>(type: "nvarchar(30)", maxLength: 30, nullable: false, comment: "거래쌍 (KRW-BTC 등)")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_CoinMarket", x => x.ID);
+                    table.ForeignKey(
+                        name: "FK_CoinMarket_Coin_CoinID",
+                        column: x => x.CoinID,
+                        principalTable: "Coin",
+                        principalColumn: "ID",
+                        onDelete: ReferentialAction.Cascade);
+                },
+                comment: "코인-거래쌍 연결");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_CoinMarket_CoinID_Market",
+                table: "CoinMarket",
+                columns: new[] { "CoinID", "Market" },
+                unique: true);
+
+            migrationBuilder.CreateIndex(
+                name: "IX_CoinMarket_Market",
+                table: "CoinMarket",
+                column: "Market");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropTable(
+                name: "CoinMarket");
+
+            migrationBuilder.AddColumn<string>(
+                name: "Market",
+                table: "Coin",
+                type: "nvarchar(30)",
+                maxLength: 30,
+                nullable: false,
+                defaultValue: "",
+                comment: "거래쌍 (KRW-BTC 등)");
+        }
+    }
+}

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

@@ -252,6 +252,38 @@ namespace Infrastructure.Persistence.Migrations
                         });
                 });
 
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinMarket", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("int")
+                        .HasComment("PK");
+
+                    SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
+
+                    b.Property<int>("CoinID")
+                        .HasColumnType("int")
+                        .HasComment("코인 ID");
+
+                    b.Property<string>("Market")
+                        .IsRequired()
+                        .HasMaxLength(30)
+                        .HasColumnType("nvarchar(30)")
+                        .HasComment("거래쌍 (KRW-BTC 등)");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Market");
+
+                    b.HasIndex("CoinID", "Market")
+                        .IsUnique();
+
+                    b.ToTable("CoinMarket", null, t =>
+                        {
+                            t.HasComment("코인-거래쌍 연결");
+                        });
+                });
+
             modelBuilder.Entity("Domain.Entities.Director.AdminAccessLog", b =>
                 {
                     b.Property<long>("ID")
@@ -4416,6 +4448,17 @@ namespace Infrastructure.Persistence.Migrations
                     b.Navigation("CoinCategory");
                 });
 
+            modelBuilder.Entity("Domain.Entities.Crypto.CoinMarket", b =>
+                {
+                    b.HasOne("Domain.Entities.Crypto.Coin", "Coin")
+                        .WithMany("CoinMarket")
+                        .HasForeignKey("CoinID")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Coin");
+                });
+
             modelBuilder.Entity("Domain.Entities.Forum.Boards.Board", b =>
                 {
                     b.HasOne("Domain.Entities.Forum.Boards.BoardGroup", "BoardGroup")
@@ -6008,6 +6051,8 @@ namespace Infrastructure.Persistence.Migrations
             modelBuilder.Entity("Domain.Entities.Crypto.Coin", b =>
                 {
                     b.Navigation("CoinCategoryMap");
+
+                    b.Navigation("CoinMarket");
                 });
 
             modelBuilder.Entity("Domain.Entities.Crypto.CoinCategory", b =>

+ 3 - 1
SharedKernel/SharedKernel.csproj

@@ -7,7 +7,9 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <FrameworkReference Include="Microsoft.AspNetCore.App" />
+    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="10.0.2" />
+    <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.9" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.9" />
   </ItemGroup>
 
 </Project>

+ 31 - 35
Web.Api/Endpoints/Crypto/CryptoCandles.cs

@@ -7,103 +7,99 @@ internal sealed class CryptoCandles : IEndpoint
 {
     public void MapEndpoint(IEndpointRouteBuilder app)
     {
-        app.MapGet("api/crypto/{symbol}/candles/seconds", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/seconds", async (
+            string market,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetSeconds.Query(symbol, count ?? 200), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetSeconds.Query(NormalizeMarket(market), count ?? 200), ct);
+            return ApiResponse.Ok(response.Candles);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/candles/minutes/{unit:int}", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/minutes/{unit:int}", async (
+            string market,
             int unit,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetMinutes.Query(symbol, unit, count ?? 200), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetMinutes.Query(NormalizeMarket(market), unit, count ?? 200), ct);
+            return ApiResponse.Ok(response.Candles);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/candles/days", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/days", async (
+            string market,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetDays.Query(symbol, count ?? 200), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetDays.Query(NormalizeMarket(market), count ?? 200), ct);
+            return ApiResponse.Ok(response.Candles);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/candles/weeks", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/weeks", async (
+            string market,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetWeeks.Query(symbol, count ?? 200), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetWeeks.Query(NormalizeMarket(market), count ?? 200), ct);
+            return ApiResponse.Ok(response.Candles);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/candles/months", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/months", async (
+            string market,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetMonths.Query(symbol, count ?? 200), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetMonths.Query(NormalizeMarket(market), count ?? 200), ct);
+            return ApiResponse.Ok(response.Candles);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/candles/years", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/years", async (
+            string market,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetYears.Query(symbol, count ?? 200), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetYears.Query(NormalizeMarket(market), count ?? 200), ct);
+            return ApiResponse.Ok(response.Candles);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/candles/live/{interval}", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/candles/live/{interval}", async (
+            string market,
             string interval,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Candle.GetLive.Query(symbol, interval), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Candle.GetLive.Query(NormalizeMarket(market), interval), ct);
+            return ApiResponse.Ok(response.Candle);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
     }
+
+    private static string NormalizeMarket(string input) =>
+        input.Contains('-') ? input.ToUpper() : $"KRW-{input.ToUpper()}";
 }

+ 3 - 3
Web.Api/Endpoints/Crypto/CryptoMarkets.cs

@@ -12,9 +12,9 @@ internal sealed class CryptoMarkets : IEndpoint
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Market.GetAll.Query(), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Market.GetAll.Query(), ct);
+
+            return ApiResponse.Ok(response.Markets);
         })
         .WithTags("Crypto")
         .AllowAnonymous();

+ 9 - 6
Web.Api/Endpoints/Crypto/CryptoOrderbook.cs

@@ -7,30 +7,33 @@ internal sealed class CryptoOrderbook : IEndpoint
 {
     public void MapEndpoint(IEndpointRouteBuilder app)
     {
-        app.MapGet("api/crypto/{symbol}/orderbook", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/orderbook", async (
+            string market,
             ISender sender,
             CancellationToken ct
         ) =>
         {
             return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Orderbook.Get.Query(symbol), ct)
+                await sender.Send(new Application.Features.Api.Crypto.Orderbook.Get.Query(NormalizeMarket(market)), ct)
             );
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/orderbook/live", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/orderbook/live", async (
+            string market,
             ISender sender,
             CancellationToken ct
         ) =>
         {
             return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Orderbook.GetLive.Query(symbol), ct)
+                await sender.Send(new Application.Features.Api.Crypto.Orderbook.GetLive.Query(NormalizeMarket(market)), ct)
             );
         })
         .WithTags("Crypto")
         .AllowAnonymous();
     }
+
+    private static string NormalizeMarket(string input) =>
+        input.Contains('-') ? input.ToUpper() : $"KRW-{input.ToUpper()}";
 }

+ 15 - 9
Web.Api/Endpoints/Crypto/CryptoTickers.cs

@@ -7,41 +7,47 @@ internal sealed class CryptoTickers : IEndpoint
 {
     public void MapEndpoint(IEndpointRouteBuilder app)
     {
+        // 전체 코인 조회
         app.MapGet("api/crypto/tickers", async (
+            string? quote,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Ticker.GetAll.Query(false), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Ticker.GetAll.Query(quote), ct);
+            return ApiResponse.Ok(response.Tickers);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
+        // 선별된 코인 조회
         app.MapGet("api/crypto/tickers/featured", async (
+            string? quote,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Ticker.GetAll.Query(true), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Ticker.GetAll.Query(quote, true), ct);
+            return ApiResponse.Ok(response.Tickers);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/ticker", async (
-            string symbol,
+        // 페어 단위 현재가 조회
+        app.MapGet("api/crypto/{market}/ticker", async (
+            string market,
             ISender sender,
             CancellationToken ct
         ) =>
         {
             return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Ticker.GetDetail.Query(symbol), ct)
+                await sender.Send(new Application.Features.Api.Crypto.Ticker.GetDetail.Query(NormalizeMarket(market)), ct)
             );
         })
         .WithTags("Crypto")
         .AllowAnonymous();
     }
+
+    private static string NormalizeMarket(string input) =>
+        input.Contains('-') ? input.ToUpper() : $"KRW-{input.ToUpper()}";
 }

+ 11 - 10
Web.Api/Endpoints/Crypto/CryptoTrades.cs

@@ -7,31 +7,32 @@ internal sealed class CryptoTrades : IEndpoint
 {
     public void MapEndpoint(IEndpointRouteBuilder app)
     {
-        app.MapGet("api/crypto/{symbol}/trades", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/trades", async (
+            string market,
             int? count,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Trade.GetRecent.Query(symbol, count ?? 50), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Trade.GetRecent.Query(NormalizeMarket(market), count ?? 50), ct);
+            return ApiResponse.Ok(response.Trades);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
 
-        app.MapGet("api/crypto/{symbol}/trades/live", async (
-            string symbol,
+        app.MapGet("api/crypto/{market}/trades/live", async (
+            string market,
             ISender sender,
             CancellationToken ct
         ) =>
         {
-            return ApiResponse.Ok(
-                await sender.Send(new Application.Features.Api.Crypto.Trade.GetLive.Query(symbol), ct)
-            );
+            var response = await sender.Send(new Application.Features.Api.Crypto.Trade.GetLive.Query(NormalizeMarket(market)), ct);
+            return ApiResponse.Ok(response.Trade);
         })
         .WithTags("Crypto")
         .AllowAnonymous();
     }
+
+    private static string NormalizeMarket(string input) =>
+        input.Contains('-') ? input.ToUpper() : $"KRW-{input.ToUpper()}";
 }

+ 53 - 0
Web.Api/Hubs/CryptoHub.cs

@@ -0,0 +1,53 @@
+using Application.Abstractions.Crypto;
+using Microsoft.AspNetCore.SignalR;
+
+namespace Web.Api.Hubs;
+
+public sealed class CryptoHub : Hub<ICryptoHubClient>
+{
+    // ─── Market-specific subscriptions ────────────────────────
+
+    public async Task SubscribeMarket(string market)
+    {
+        if (string.IsNullOrWhiteSpace(market))
+        {
+            return;
+        }
+
+        var lower = market.ToLower().Trim();
+
+        await Groups.AddToGroupAsync(Context.ConnectionId, $"ticker:{lower}");
+        await Groups.AddToGroupAsync(Context.ConnectionId, $"trade:{lower}");
+        await Groups.AddToGroupAsync(Context.ConnectionId, $"orderbook:{lower}");
+        await Groups.AddToGroupAsync(Context.ConnectionId, $"candle:{lower}");
+    }
+
+    public async Task UnsubscribeMarket(string market)
+    {
+        if (string.IsNullOrWhiteSpace(market))
+        {
+            return;
+        }
+
+        var lower = market.ToLower().Trim();
+
+        await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"ticker:{lower}");
+        await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"trade:{lower}");
+        await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"orderbook:{lower}");
+        await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"candle:{lower}");
+    }
+
+    // ─── Global tickers subscription ──────────────────────────
+
+    public async Task SubscribeTickers(string? quote = null)
+    {
+        var group = string.IsNullOrWhiteSpace(quote) ? "tickers" : $"tickers:{quote.ToLower().Trim()}";
+        await Groups.AddToGroupAsync(Context.ConnectionId, group);
+    }
+
+    public async Task UnsubscribeTickers(string? quote = null)
+    {
+        var group = string.IsNullOrWhiteSpace(quote) ? "tickers" : $"tickers:{quote.ToLower().Trim()}";
+        await Groups.RemoveFromGroupAsync(Context.ConnectionId, group);
+    }
+}

+ 42 - 0
Web.Api/Hubs/CryptoHubService.cs

@@ -0,0 +1,42 @@
+using Application.Abstractions.Crypto;
+using Microsoft.AspNetCore.SignalR;
+
+namespace Web.Api.Hubs;
+
+public sealed class CryptoHubService(
+    IHubContext<CryptoHub, ICryptoHubClient> hubContext
+) : ICryptoHubService
+{
+    public async Task SendTickerAsync(CryptoHubData.TickerData ticker, CancellationToken ct)
+    {
+        await hubContext.Clients.Group($"ticker:{ticker.Market.ToLower()}").ReceiveTicker(ticker);
+    }
+
+    public async Task SendTickersAsync(IReadOnlyList<CryptoHubData.TickerData> tickers, CancellationToken ct)
+    {
+        if (tickers.Count == 0)
+        {
+            return;
+        }
+
+        var quote = tickers[0].Market.Split('-')[0].ToLower();
+
+        await hubContext.Clients.Group($"tickers:{quote}").ReceiveTickers(tickers);
+        await hubContext.Clients.Group("tickers").ReceiveTickers(tickers);
+    }
+
+    public async Task SendTradeAsync(CryptoHubData.TradeData trade, CancellationToken ct)
+    {
+        await hubContext.Clients.Group($"trade:{trade.Market.ToLower()}").ReceiveTrade(trade);
+    }
+
+    public async Task SendOrderbookAsync(CryptoHubData.OrderbookData orderbook, CancellationToken ct)
+    {
+        await hubContext.Clients.Group($"orderbook:{orderbook.Market.ToLower()}").ReceiveOrderbook(orderbook);
+    }
+
+    public async Task SendCandleAsync(CryptoHubData.CandleData candle, CancellationToken ct)
+    {
+        await hubContext.Clients.Group($"candle:{candle.Market.ToLower()}").ReceiveCandle(candle);
+    }
+}

+ 7 - 0
Web.Api/Program.cs

@@ -1,8 +1,10 @@
 using Application;
+using Application.Abstractions.Crypto;
 using Infrastructure;
 using SharedKernel;
 using Web.Api;
 using Web.Api.Extensions;
+using Web.Api.Hubs;
 using System.Reflection;
 
 var builder = WebApplication.CreateBuilder(args);
@@ -33,6 +35,10 @@ builder.Services.AddCors(options =>
     });
 });
 
+// SignalR
+builder.Services.AddSignalR();
+builder.Services.AddSingleton<ICryptoHubService, CryptoHubService>();
+
 builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
 builder.Logging.AddConsole();
 
@@ -60,5 +66,6 @@ app.UseCors(settings.CorsPolicy.Name);
 app.UseAuthentication();
 app.UseAuthorization();
 app.MapEndpoints();
+app.MapHub<CryptoHub>("/hubs/crypto");
 
 app.Run();

+ 0 - 390
Web.Api/bitforum-crypto-api.postman_collection.json

@@ -1,390 +0,0 @@
-{
-	"info": {
-		"_postman_id": "bf-crypto-api-collection",
-		"name": "bitForum Crypto API",
-		"description": "bitForum 암호화폐 Quotation API 컬렉션\n\n- REST API: 초기 로딩용 (마켓, 시세, 체결, 호가, 캔들)\n- Live API: WebSocket → Redis 실시간 데이터 조회",
-		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
-	},
-	"variable": [
-		{
-			"key": "baseUrl",
-			"value": "https://localhost:4000",
-			"type": "string"
-		},
-		{
-			"key": "symbol",
-			"value": "BTC",
-			"type": "string"
-		}
-	],
-	"item": [
-		{
-			"name": "Market",
-			"item": [
-				{
-					"name": "마켓 목록 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/markets",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "markets"]
-						},
-						"description": "KRW 거래 가능 마켓 전체 목록을 조회합니다.\n\n- 캐시: 30분\n- Upbit API: GET /v1/market/all"
-					},
-					"response": []
-				}
-			]
-		},
-		{
-			"name": "Ticker",
-			"item": [
-				{
-					"name": "시세 목록 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/tickers",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "tickers"]
-						},
-						"description": "활성화된 전체 코인의 실시간 시세 목록을 조회합니다.\n\n- 소스: Redis(WebSocket) + DB(코인 정보) 병합"
-					},
-					"response": []
-				},
-				{
-					"name": "추천 시세 목록 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/tickers/featured",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "tickers", "featured"]
-						},
-						"description": "큐레이션된 추천 코인의 실시간 시세 목록을 조회합니다.\n\n- 소스: Redis(WebSocket) + DB(코인 정보) 병합\n- IsFeatured = true 인 코인만 필터링"
-					},
-					"response": []
-				},
-				{
-					"name": "현재가 상세 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/ticker",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "ticker"]
-						},
-						"description": "특정 코인의 현재가 상세 정보를 조회합니다.\n\n- 캐시: 10초\n- Upbit API: GET /v1/ticker\n- 응답: 시가, 고가, 저가, 종가, 전일종가, 52주 최고/최저가, 누적 거래량/금액 등 17개 필드"
-					},
-					"response": []
-				}
-			]
-		},
-		{
-			"name": "Trade",
-			"item": [
-				{
-					"name": "최근 체결 내역 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/trades?count=50",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "trades"],
-							"query": [
-								{
-									"key": "count",
-									"value": "50",
-									"description": "체결 내역 개수 (1~100, 기본값: 50)"
-								}
-							]
-						},
-						"description": "특정 코인의 최근 체결 내역을 조회합니다.\n\n- 캐시: 5초\n- Upbit API: GET /v1/trades/ticks\n- 응답: 체결가, 체결량, 매수/매도(ASK/BID), 체결번호"
-					},
-					"response": []
-				},
-				{
-					"name": "실시간 체결 조회 (Live)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/trades/live",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "trades", "live"]
-						},
-						"description": "WebSocket으로 수신된 최신 체결 1건을 조회합니다.\n\n- 소스: Redis (WebSocket → Redis 저장)\n- 응답: 체결가, 체결량, 매수/매도, 타임스탬프"
-					},
-					"response": []
-				}
-			]
-		},
-		{
-			"name": "Orderbook",
-			"item": [
-				{
-					"name": "호가 정보 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/orderbook",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "orderbook"]
-						},
-						"description": "특정 코인의 호가 정보를 조회합니다.\n\n- 캐시: 5초\n- Upbit API: GET /v1/orderbook\n- 응답: 매도/매수 호가 리스트, 매도/매수 잔량 합계"
-					},
-					"response": []
-				},
-				{
-					"name": "실시간 호가 조회 (Live)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/orderbook/live",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "orderbook", "live"]
-						},
-						"description": "WebSocket으로 수신된 실시간 호가 정보를 조회합니다.\n\n- 소스: Redis (WebSocket → Redis 저장)\n- 응답: 매도/매수 호가 리스트, 잔량 합계, 타임스탬프"
-					},
-					"response": []
-				}
-			]
-		},
-		{
-			"name": "Candle",
-			"item": [
-				{
-					"name": "초봉 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/seconds?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "seconds"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "초봉 캔들 데이터를 조회합니다.\n\n- 캐시: 1분\n- Upbit API: GET /v1/candles/seconds"
-					},
-					"response": []
-				},
-				{
-					"name": "분봉 조회 (1분)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/minutes/1?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "minutes", "1"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "분봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/minutes/{unit}\n- 허용 unit: 1, 3, 5, 10, 15, 30, 60, 240"
-					},
-					"response": []
-				},
-				{
-					"name": "분봉 조회 (5분)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/minutes/5?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "minutes", "5"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "5분봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/minutes/5"
-					},
-					"response": []
-				},
-				{
-					"name": "분봉 조회 (15분)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/minutes/15?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "minutes", "15"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "15분봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/minutes/15"
-					},
-					"response": []
-				},
-				{
-					"name": "분봉 조회 (60분)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/minutes/60?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "minutes", "60"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "60분봉 (1시간) 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/minutes/60"
-					},
-					"response": []
-				},
-				{
-					"name": "분봉 조회 (240분)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/minutes/240?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "minutes", "240"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "240분봉 (4시간) 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/minutes/240"
-					},
-					"response": []
-				},
-				{
-					"name": "일봉 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/days?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "days"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "일봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/days"
-					},
-					"response": []
-				},
-				{
-					"name": "주봉 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/weeks?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "weeks"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "주봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/weeks"
-					},
-					"response": []
-				},
-				{
-					"name": "월봉 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/months?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "months"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "월봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/months"
-					},
-					"response": []
-				},
-				{
-					"name": "연봉 조회",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/years?count=200",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "years"],
-							"query": [
-								{
-									"key": "count",
-									"value": "200",
-									"description": "캔들 개수 (1~200, 기본값: 200)"
-								}
-							]
-						},
-						"description": "연봉 캔들 데이터를 조회합니다.\n\n- 캐시: 5분\n- Upbit API: GET /v1/candles/years"
-					},
-					"response": []
-				},
-				{
-					"name": "실시간 캔들 조회 (Live)",
-					"request": {
-						"method": "GET",
-						"header": [],
-						"url": {
-							"raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/live/m1",
-							"host": ["{{baseUrl}}"],
-							"path": ["api", "crypto", "{{symbol}}", "candles", "live", "m1"]
-						},
-						"description": "WebSocket으로 수신된 실시간 1분봉 캔들 1건을 조회합니다.\n\n- 소스: Redis (WebSocket → Redis 저장)\n- interval: m1 (1분봉)\n- 응답: 시가, 고가, 저가, 종가, 거래량, 거래금액"
-					},
-					"response": []
-				}
-			]
-		}
-	]
-}

+ 0 - 333
Web.Api/bitforum-forum-api.postman_collection.json

@@ -1,333 +0,0 @@
-{
-  "info": {
-    "name": "bitForum - Forum API",
-    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
-  },
-  "auth": {
-    "type": "bearer",
-    "bearer": [
-      {
-        "key": "token",
-        "value": "{{accessToken}}",
-        "type": "string"
-      }
-    ]
-  },
-  "variable": [
-    {
-      "key": "baseUrl",
-      "value": "https://localhost:4000"
-    },
-    {
-      "key": "accessToken",
-      "value": ""
-    }
-  ],
-  "item": [
-    {
-      "name": "Board",
-      "item": [
-        {
-          "name": "Search Boards",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/boards?pageNum=1&perPage=20",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "boards"],
-              "query": [
-                { "key": "boardGroupID", "value": "", "disabled": true },
-                { "key": "keyword", "value": "", "disabled": true },
-                { "key": "pageNum", "value": "1" },
-                { "key": "perPage", "value": "20" }
-              ]
-            },
-            "auth": { "type": "noauth" }
-          }
-        },
-        {
-          "name": "Get Board",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/boards/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "boards", "1"]
-            },
-            "auth": { "type": "noauth" }
-          }
-        },
-        {
-          "name": "Get Board Meta",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/boards/1/meta",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "boards", "1", "meta"]
-            },
-            "auth": { "type": "noauth" }
-          }
-        }
-      ]
-    },
-    {
-      "name": "Post",
-      "item": [
-        {
-          "name": "Search Posts",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts?pageNum=1&perPage=20",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts"],
-              "query": [
-                { "key": "boardID", "value": "", "disabled": true },
-                { "key": "search", "value": "", "disabled": true },
-                { "key": "keyword", "value": "", "disabled": true },
-                { "key": "startAt", "value": "", "disabled": true },
-                { "key": "endAt", "value": "", "disabled": true },
-                { "key": "sort", "value": "", "disabled": true },
-                { "key": "isNotice", "value": "", "disabled": true },
-                { "key": "isSecret", "value": "", "disabled": true },
-                { "key": "isReply", "value": "", "disabled": true },
-                { "key": "isDeleted", "value": "", "disabled": true },
-                { "key": "pageNum", "value": "1" },
-                { "key": "perPage", "value": "20" }
-              ]
-            },
-            "auth": { "type": "noauth" }
-          }
-        },
-        {
-          "name": "Get Post",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts", "1"]
-            },
-            "auth": { "type": "noauth" }
-          }
-        },
-        {
-          "name": "Create Post",
-          "request": {
-            "method": "POST",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"boardID\": 1,\n  \"boardPrefixID\": null,\n  \"subject\": \"테스트 제목\",\n  \"content\": \"테스트 내용\",\n  \"isSecret\": false\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts"]
-            }
-          }
-        },
-        {
-          "name": "Update Post",
-          "request": {
-            "method": "PUT",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"boardPrefixID\": null,\n  \"subject\": \"수정된 제목\",\n  \"content\": \"수정된 내용\",\n  \"isSecret\": false\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts", "1"]
-            }
-          }
-        },
-        {
-          "name": "Delete Post",
-          "request": {
-            "method": "DELETE",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts", "1"]
-            }
-          }
-        },
-        {
-          "name": "Toggle Post Reaction",
-          "request": {
-            "method": "POST",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"reaction\": 0\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts/1/reaction",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts", "1", "reaction"]
-            }
-          }
-        },
-        {
-          "name": "Report Post",
-          "request": {
-            "method": "POST",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"type\": 0,\n  \"reason\": \"신고 사유\"\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/posts/1/report",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "posts", "1", "report"]
-            }
-          }
-        }
-      ]
-    },
-    {
-      "name": "Comment",
-      "item": [
-        {
-          "name": "Search Comments",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments?pageNum=1&perPage=20",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments"],
-              "query": [
-                { "key": "boardID", "value": "", "disabled": true },
-                { "key": "postID", "value": "", "disabled": true },
-                { "key": "search", "value": "", "disabled": true },
-                { "key": "keyword", "value": "", "disabled": true },
-                { "key": "startAt", "value": "", "disabled": true },
-                { "key": "endAt", "value": "", "disabled": true },
-                { "key": "isDeleted", "value": "", "disabled": true },
-                { "key": "pageNum", "value": "1" },
-                { "key": "perPage", "value": "20" }
-              ]
-            },
-            "auth": { "type": "noauth" }
-          }
-        },
-        {
-          "name": "Get Comment",
-          "request": {
-            "method": "GET",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments", "1"]
-            },
-            "auth": { "type": "noauth" }
-          }
-        },
-        {
-          "name": "Create Comment",
-          "request": {
-            "method": "POST",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"boardID\": 1,\n  \"postID\": 1,\n  \"parentID\": null,\n  \"content\": \"테스트 댓글\",\n  \"isSecret\": false\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments"]
-            }
-          }
-        },
-        {
-          "name": "Update Comment",
-          "request": {
-            "method": "PUT",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"content\": \"수정된 댓글\",\n  \"isSecret\": false\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments", "1"]
-            }
-          }
-        },
-        {
-          "name": "Delete Comment",
-          "request": {
-            "method": "DELETE",
-            "header": [],
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments/1",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments", "1"]
-            }
-          }
-        },
-        {
-          "name": "Toggle Comment Reaction",
-          "request": {
-            "method": "POST",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"reaction\": 0\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments/1/reaction",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments", "1", "reaction"]
-            }
-          }
-        },
-        {
-          "name": "Report Comment",
-          "request": {
-            "method": "POST",
-            "header": [
-              { "key": "Content-Type", "value": "application/json" }
-            ],
-            "body": {
-              "mode": "raw",
-              "raw": "{\n  \"type\": 0,\n  \"reason\": \"신고 사유\"\n}"
-            },
-            "url": {
-              "raw": "{{baseUrl}}/api/forum/comments/1/report",
-              "host": ["{{baseUrl}}"],
-              "path": ["api", "forum", "comments", "1", "report"]
-            }
-          }
-        }
-      ]
-    }
-  ]
-}

+ 347 - 0
bitforum-crypto.postman_collection.json

@@ -0,0 +1,347 @@
+{
+  "info": {
+    "name": "bitForum Crypto API",
+    "description": "bitForum 코인 관련 API 엔드포인트 (https://api.bitforum.io)",
+    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+  },
+  "variable": [
+    {
+      "key": "baseUrl",
+      "value": "https://localhost:4000",
+      "type": "string"
+    },
+    {
+      "key": "symbol",
+      "value": "btc",
+      "type": "string"
+    }
+  ],
+  "item": [
+    {
+      "name": "Tickers",
+      "description": "현재가 관련 API",
+      "item": [
+        {
+          "name": "전체 Ticker 목록",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/tickers",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "tickers"]
+            },
+            "description": "등록된 모든 코인의 현재가 목록을 조회합니다."
+          }
+        },
+        {
+          "name": "주요 Ticker 목록",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/tickers/featured",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "tickers", "featured"]
+            },
+            "description": "주요(Featured) 코인의 현재가 목록을 조회합니다."
+          }
+        },
+        {
+          "name": "Ticker 상세",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/ticker",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "ticker"]
+            },
+            "description": "특정 코인의 현재가 상세 정보를 조회합니다.\n\nRoute Params:\n- symbol: 코인 심볼 (예: btc, eth, xrp)"
+          }
+        }
+      ]
+    },
+    {
+      "name": "Candles",
+      "description": "캔들(차트) 관련 API",
+      "item": [
+        {
+          "name": "초(Seconds) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/seconds?count=200",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "seconds"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "200",
+                  "description": "조회할 캔들 수 (1~200, 기본값: 200)"
+                }
+              ]
+            },
+            "description": "특정 코인의 초 단위 캔들 데이터를 조회합니다.\n\n최근 3개월 이내 데이터만 조회 가능합니다."
+          }
+        },
+        {
+          "name": "분(Minutes) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/minutes/1?count=200",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "minutes", "1"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "200",
+                  "description": "조회할 캔들 수 (1~200, 기본값: 200)"
+                }
+              ]
+            },
+            "description": "특정 코인의 분 단위 캔들 데이터를 조회합니다.\n\nRoute Params:\n- unit: 캔들 집계 단위 (1, 3, 5, 10, 15, 30, 60, 240분)"
+          }
+        },
+        {
+          "name": "일(Days) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/days?count=200",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "days"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "200",
+                  "description": "조회할 캔들 수 (1~200, 기본값: 200)"
+                }
+              ]
+            },
+            "description": "특정 코인의 일 단위 캔들 데이터를 조회합니다.\n\n추가 필드: prevClosingPrice, changePrice, changeRate"
+          }
+        },
+        {
+          "name": "주(Weeks) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/weeks?count=200",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "weeks"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "200",
+                  "description": "조회할 캔들 수 (1~200, 기본값: 200)"
+                }
+              ]
+            },
+            "description": "특정 코인의 주 단위 캔들 데이터를 조회합니다.\n\n추가 필드: firstDayOfPeriod (캔들 집계 시작일)"
+          }
+        },
+        {
+          "name": "월(Months) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/months?count=200",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "months"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "200",
+                  "description": "조회할 캔들 수 (1~200, 기본값: 200)"
+                }
+              ]
+            },
+            "description": "특정 코인의 월 단위 캔들 데이터를 조회합니다.\n\n추가 필드: firstDayOfPeriod (캔들 집계 시작일)"
+          }
+        },
+        {
+          "name": "연(Years) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/years?count=200",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "years"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "200",
+                  "description": "조회할 캔들 수 (1~200, 기본값: 200)"
+                }
+              ]
+            },
+            "description": "특정 코인의 연 단위 캔들 데이터를 조회합니다.\n\n추가 필드: firstDayOfPeriod (캔들 집계 시작일)"
+          }
+        },
+        {
+          "name": "실시간(Live) 캔들",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/candles/live/m1",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "candles", "live", "m1"]
+            },
+            "description": "특정 코인의 실시간 캔들 데이터를 조회합니다 (Redis 캐시).\n\nRoute Params:\n- interval: 캔들 간격 (예: m1, m3, m5, m10, m15, m30, m60, m240)"
+          }
+        }
+      ]
+    },
+    {
+      "name": "Trades",
+      "description": "체결 내역 관련 API",
+      "item": [
+        {
+          "name": "최근 체결 내역",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/trades?count=50",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "trades"],
+              "query": [
+                {
+                  "key": "count",
+                  "value": "50",
+                  "description": "조회할 체결 수 (1~100, 기본값: 50)"
+                }
+              ]
+            },
+            "description": "특정 코인의 최근 체결 내역을 조회합니다 (Upbit REST API)."
+          }
+        },
+        {
+          "name": "실시간(Live) 체결",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/trades/live",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "trades", "live"]
+            },
+            "description": "특정 코인의 실시간 체결 데이터를 조회합니다 (Redis 캐시, WebSocket 수신 데이터)."
+          }
+        }
+      ]
+    },
+    {
+      "name": "Orderbook",
+      "description": "호가 정보 관련 API",
+      "item": [
+        {
+          "name": "호가 정보",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/orderbook",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "orderbook"]
+            },
+            "description": "특정 코인의 호가(매수/매도) 정보를 조회합니다 (Upbit REST API)."
+          }
+        },
+        {
+          "name": "실시간(Live) 호가",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/{{symbol}}/orderbook/live",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "{{symbol}}", "orderbook", "live"]
+            },
+            "description": "특정 코인의 실시간 호가 데이터를 조회합니다 (Redis 캐시, WebSocket 수신 데이터)."
+          }
+        }
+      ]
+    },
+    {
+      "name": "Markets",
+      "description": "마켓 목록 관련 API",
+      "item": [
+        {
+          "name": "전체 마켓 목록",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/api/crypto/markets",
+              "host": ["{{baseUrl}}"],
+              "path": ["api", "crypto", "markets"]
+            },
+            "description": "Upbit에 등록된 전체 마켓(거래쌍) 목록을 조회합니다.\n\n마켓 이벤트(유의 종목, 경보) 정보를 포함합니다."
+          }
+        }
+      ]
+    },
+    {
+      "name": "SignalR Hub (WebSocket)",
+      "description": "실시간 데이터 Push — SignalR Hub\n\nWebSocket URL: wss://localhost:4000/hubs/crypto\n운영: wss://api.bitforum.io/hubs/crypto\n\n── 연결 방법 (Postman WebSocket 탭) ──\n\n1. Postman > New > WebSocket 선택\n2. URL: wss://localhost:4000/hubs/crypto 입력 후 Connect\n3. 핸드셰이크 메시지 전송:\n   {\"protocol\":\"json\",\"version\":1}\\x1e\n4. 구독 메시지 전송 (아래 예시 참고)\n\n── Hub 메서드 (클라이언트 → 서버) ──\n\nSubscribeSymbol: 특정 코인 구독 (ticker/trade/orderbook/candle)\n  {\"type\":1,\"target\":\"SubscribeSymbol\",\"arguments\":[\"btc\"]}\\x1e\n\nUnsubscribeSymbol: 특정 코인 구독 해제\n  {\"type\":1,\"target\":\"UnsubscribeSymbol\",\"arguments\":[\"btc\"]}\\x1e\n\nSubscribeTickers: 전체 Ticker 목록 구독\n  {\"type\":1,\"target\":\"SubscribeTickers\",\"arguments\":[]}\\x1e\n\nUnsubscribeTickers: 전체 Ticker 목록 구독 해제\n  {\"type\":1,\"target\":\"UnsubscribeTickers\",\"arguments\":[]}\\x1e\n\n── 수신 이벤트 (서버 → 클라이언트) ──\n\nReceiveTicker: 개별 코인 현재가\nReceiveTickers: 전체 코인 현재가 목록\nReceiveTrade: 체결\nReceiveOrderbook: 호가\nReceiveCandle: 캔들\n\n참고: \\x1e 는 SignalR Record Separator (U+001E) — 메시지 끝 구분자",
+      "item": [
+        {
+          "name": "negotiate (연결 협상)",
+          "request": {
+            "method": "POST",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/hubs/crypto/negotiate?negotiateVersion=1",
+              "host": ["{{baseUrl}}"],
+              "path": ["hubs", "crypto", "negotiate"],
+              "query": [
+                {
+                  "key": "negotiateVersion",
+                  "value": "1",
+                  "description": "SignalR negotiate 프로토콜 버전"
+                }
+              ]
+            },
+            "description": "SignalR Hub 연결 전 협상 엔드포인트.\n\n응답으로 connectionId, availableTransports 등을 반환합니다.\nWebSocket 연결 시 자동으로 호출되지만, 직접 테스트도 가능합니다."
+          }
+        },
+        {
+          "name": "[WSS] SubscribeSymbol",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/hubs/crypto",
+              "host": ["{{baseUrl}}"],
+              "path": ["hubs", "crypto"]
+            },
+            "description": "WebSocket 연결 후 전송할 메시지:\n\n1. 핸드셰이크:\n{\"protocol\":\"json\",\"version\":1}\\x1e\n\n2. 구독:\n{\"type\":1,\"target\":\"SubscribeSymbol\",\"arguments\":[\"btc\"]}\\x1e\n\n구독 후 수신 이벤트:\n- ReceiveTicker: 해당 코인 현재가\n- ReceiveTrade: 해당 코인 체결\n- ReceiveOrderbook: 해당 코인 호가\n- ReceiveCandle: 해당 코인 캔들\n\n구독 해제:\n{\"type\":1,\"target\":\"UnsubscribeSymbol\",\"arguments\":[\"btc\"]}\\x1e\n\n※ Postman WebSocket 탭에서 wss://localhost:4000/hubs/crypto 로 직접 연결하세요."
+          }
+        },
+        {
+          "name": "[WSS] SubscribeTickers",
+          "request": {
+            "method": "GET",
+            "header": [],
+            "url": {
+              "raw": "{{baseUrl}}/hubs/crypto",
+              "host": ["{{baseUrl}}"],
+              "path": ["hubs", "crypto"]
+            },
+            "description": "WebSocket 연결 후 전송할 메시지:\n\n1. 핸드셰이크:\n{\"protocol\":\"json\",\"version\":1}\\x1e\n\n2. 전체 Ticker 구독:\n{\"type\":1,\"target\":\"SubscribeTickers\",\"arguments\":[]}\\x1e\n\n구독 후 수신 이벤트:\n- ReceiveTickers: 전체 코인 현재가 목록 (주기적 갱신)\n- ReceiveTicker: 개별 코인 현재가 (변동 시)\n\n구독 해제:\n{\"type\":1,\"target\":\"UnsubscribeTickers\",\"arguments\":[]}\\x1e\n\n※ Postman WebSocket 탭에서 wss://localhost:4000/hubs/crypto 로 직접 연결하세요."
+          }
+        }
+      ]
+    }
+  ]
+}

+ 5 - 0
global.json

@@ -0,0 +1,5 @@
+{
+  "sdk": {
+    "version": "10.0.103"
+  }
+}