using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Infrastructure.Migrations.AppDb;
///
public partial class IntroduceFeedDomain : Migration
{
///
protected override void Up(MigrationBuilder migrationBuilder)
{
// ─────────────────────────────────────────────────────────
// 기존 Feed 보드(Board.Code = N'feed') 관련 데이터 전부 삭제
// 새 독립 Feed 도메인으로 이행하는 과정에서 구조 불일치가 있어
// 호환 마이그레이션 대신 구버전 데이터를 명시적으로 제거한다.
// ─────────────────────────────────────────────────────────
migrationBuilder.Sql(@"
DECLARE @FeedBoardID INT = (SELECT TOP 1 ID FROM Board WHERE Code = N'feed');
IF @FeedBoardID IS NOT NULL
BEGIN
-- MemberStats.PostCount 역추적 차감 (feed 보드 작성자별 합계만큼 줄임)
UPDATE MS
SET MS.PostCount = CASE WHEN MS.PostCount > cnt.cnt THEN MS.PostCount - cnt.cnt ELSE 0 END
FROM MemberStats MS
INNER JOIN (
SELECT MemberID, COUNT(*) AS cnt
FROM Post
WHERE BoardID = @FeedBoardID AND MemberID IS NOT NULL
GROUP BY MemberID
) cnt ON MS.MemberID = cnt.MemberID;
-- Tag.UsageCount 역추적 차감 (feed 보드 포스트가 사용한 태그 횟수만큼 줄임)
UPDATE T
SET T.UsageCount = CASE WHEN T.UsageCount > cnt.cnt THEN T.UsageCount - cnt.cnt ELSE 0 END
FROM Tag T
INNER JOIN (
SELECT PT.TagID, COUNT(*) AS cnt
FROM PostTag PT
INNER JOIN Post P ON PT.PostID = P.ID
WHERE P.BoardID = @FeedBoardID
GROUP BY PT.TagID
) cnt ON T.ID = cnt.TagID;
-- Post 삭제 (Cascade로 PostImage/Media/File/Link/Tag/Reaction/Bookmark/Report/Comment 등 전부 삭제)
DELETE FROM Post WHERE BoardID = @FeedBoardID;
-- Board 부속 엔티티 삭제
DELETE FROM BoardPrefix WHERE BoardID = @FeedBoardID;
DELETE FROM BoardManager WHERE BoardID = @FeedBoardID;
DELETE FROM BoardMeta WHERE BoardID = @FeedBoardID;
-- Board 자체 삭제
DELETE FROM Board WHERE ID = @FeedBoardID;
END;
");
migrationBuilder.CreateTable(
name: "FeedPost",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
MemberID = table.Column(type: "int", nullable: false, comment: "작성 회원 ID"),
Content = table.Column(type: "nvarchar(max)", maxLength: 5000, nullable: false, comment: "본문"),
Thumbnail = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true, comment: "썸네일 URL"),
Images = table.Column(type: "tinyint", nullable: false, comment: "이미지 개수"),
Medias = table.Column(type: "tinyint", nullable: false, comment: "미디어 개수"),
Tags = table.Column(type: "tinyint", nullable: false, comment: "태그 개수"),
Likes = table.Column(type: "int", nullable: false, comment: "좋아요 수"),
Comments = table.Column(type: "int", nullable: false, comment: "댓글 수"),
Bookmarks = table.Column(type: "int", nullable: false, comment: "북마크 수"),
Views = table.Column(type: "int", nullable: false, comment: "조회수"),
Reports = table.Column(type: "int", nullable: false, comment: "신고 수"),
IsDeleted = table.Column(type: "bit", nullable: false, comment: "삭제 여부"),
IpAddress = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true, comment: "작성자 IP"),
UserAgent = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true, comment: "작성자 User-Agent"),
DeletedAt = table.Column(type: "datetime2", nullable: true, comment: "삭제 일시"),
UpdatedAt = table.Column(type: "datetime2", nullable: true, comment: "수정 일시"),
CreatedAt = table.Column(type: "datetime2", nullable: false, comment: "등록 일시")
},
constraints: table =>
{
table.PrimaryKey("PK_FeedPost", x => x.ID);
table.ForeignKey(
name: "FK_FeedPost_Member_MemberID",
column: x => x.MemberID,
principalTable: "Member",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
},
comment: "피드 포스트");
migrationBuilder.CreateTable(
name: "FeedTag",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false, comment: "태그 이름"),
Slug = table.Column(type: "nvarchar(120)", maxLength: 120, nullable: false, comment: "태그 슬러그 (URL용)"),
UsageCount = table.Column(type: "bigint", nullable: false, comment: "사용 횟수"),
UpdatedAt = table.Column(type: "datetime2", nullable: true, comment: "수정 일시"),
CreatedAt = table.Column(type: "datetime2", nullable: false, comment: "등록 일시")
},
constraints: table =>
{
table.PrimaryKey("PK_FeedTag", x => x.ID);
},
comment: "피드 태그 마스터");
migrationBuilder.CreateTable(
name: "FeedBookmark",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
MemberID = table.Column(type: "int", nullable: false),
IpAddress = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
UserAgent = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedBookmark", x => x.ID);
table.ForeignKey(
name: "FK_FeedBookmark_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FeedBookmark_Member_MemberID",
column: x => x.MemberID,
principalTable: "Member",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 포스트 북마크");
migrationBuilder.CreateTable(
name: "FeedComment",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false, comment: "FeedPost ID"),
MemberID = table.Column(type: "int", nullable: false, comment: "작성 회원 ID"),
ParentID = table.Column(type: "int", nullable: true, comment: "부모 댓글 ID (대댓글용)"),
Depth = table.Column(type: "smallint", nullable: false, comment: "중첩 깊이 (0 = 루트)"),
Content = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false, comment: "댓글 내용"),
Likes = table.Column(type: "int", nullable: false, comment: "좋아요 수"),
Replies = table.Column(type: "int", nullable: false, comment: "자식 댓글 수"),
Reports = table.Column(type: "int", nullable: false, comment: "신고 수"),
Score = table.Column(type: "int", nullable: false, comment: "랭킹 스코어"),
IsDeleted = table.Column(type: "bit", nullable: false, comment: "삭제 여부"),
IpAddress = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
UserAgent = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true),
DeletedAt = table.Column(type: "datetime2", nullable: true, comment: "삭제 일시"),
UpdatedAt = table.Column(type: "datetime2", nullable: true, comment: "수정 일시"),
CreatedAt = table.Column(type: "datetime2", nullable: false, comment: "등록 일시")
},
constraints: table =>
{
table.PrimaryKey("PK_FeedComment", x => x.ID);
table.ForeignKey(
name: "FK_FeedComment_FeedComment_ParentID",
column: x => x.ParentID,
principalTable: "FeedComment",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_FeedComment_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FeedComment_Member_MemberID",
column: x => x.MemberID,
principalTable: "Member",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
},
comment: "피드 댓글 (Reddit식 계층형)");
migrationBuilder.CreateTable(
name: "FeedLike",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
MemberID = table.Column(type: "int", nullable: false),
IpAddress = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
UserAgent = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedLike", x => x.ID);
table.ForeignKey(
name: "FK_FeedLike_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FeedLike_Member_MemberID",
column: x => x.MemberID,
principalTable: "Member",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 포스트 좋아요");
migrationBuilder.CreateTable(
name: "FeedPostImage",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
UUID = table.Column(type: "uniqueidentifier", nullable: false, comment: "UUID"),
FileName = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false, comment: "원본 파일명"),
HashedName = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false, comment: "저장 파일명(해시)"),
Path = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false, comment: "상대 경로"),
Url = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false, comment: "접근 URL"),
Extension = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: true, comment: "확장자"),
ContentType = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true, comment: "MIME"),
Size = table.Column(type: "bigint", nullable: true, comment: "바이트 크기"),
Width = table.Column(type: "smallint", nullable: true, comment: "가로 픽셀"),
Height = table.Column(type: "smallint", nullable: true, comment: "세로 픽셀"),
IsDisabled = table.Column(type: "bit", nullable: false, comment: "비활성 여부"),
DisabledAt = table.Column(type: "datetime2", nullable: true, comment: "비활성 일시"),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedPostImage", x => x.ID);
table.ForeignKey(
name: "FK_FeedPostImage_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 포스트 이미지");
migrationBuilder.CreateTable(
name: "FeedPostMedia",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
Url = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false, comment: "미디어 URL"),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedPostMedia", x => x.ID);
table.ForeignKey(
name: "FK_FeedPostMedia_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 포스트 미디어 링크");
migrationBuilder.CreateTable(
name: "FeedPostMention",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
MemberID = table.Column(type: "int", nullable: false),
RawHandle = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false, comment: "원문 @handle"),
Start = table.Column(type: "int", nullable: false, comment: "본문 내 시작 인덱스"),
Length = table.Column(type: "int", nullable: false, comment: "길이"),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedPostMention", x => x.ID);
table.ForeignKey(
name: "FK_FeedPostMention_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FeedPostMention_Member_MemberID",
column: x => x.MemberID,
principalTable: "Member",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 포스트 멘션");
migrationBuilder.CreateTable(
name: "FeedPostTag",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
FeedTagID = table.Column(type: "int", nullable: false),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedPostTag", x => x.ID);
table.ForeignKey(
name: "FK_FeedPostTag_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FeedPostTag_FeedTag_FeedTagID",
column: x => x.FeedTagID,
principalTable: "FeedTag",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 포스트-태그 조인");
migrationBuilder.CreateTable(
name: "FeedCommentLike",
columns: table => new
{
ID = table.Column(type: "int", nullable: false, comment: "PK")
.Annotation("SqlServer:Identity", "1, 1"),
FeedPostID = table.Column(type: "int", nullable: false),
FeedCommentID = table.Column(type: "int", nullable: false),
MemberID = table.Column(type: "int", nullable: false),
IpAddress = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
UserAgent = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true),
CreatedAt = table.Column(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeedCommentLike", x => x.ID);
table.ForeignKey(
name: "FK_FeedCommentLike_FeedComment_FeedCommentID",
column: x => x.FeedCommentID,
principalTable: "FeedComment",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FeedCommentLike_FeedPost_FeedPostID",
column: x => x.FeedPostID,
principalTable: "FeedPost",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_FeedCommentLike_Member_MemberID",
column: x => x.MemberID,
principalTable: "Member",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
},
comment: "피드 댓글 좋아요");
migrationBuilder.CreateIndex(
name: "IX_FeedBookmark_FeedPostID",
table: "FeedBookmark",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedBookmark_FeedPostID_MemberID",
table: "FeedBookmark",
columns: new[] { "FeedPostID", "MemberID" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedBookmark_MemberID",
table: "FeedBookmark",
column: "MemberID");
migrationBuilder.CreateIndex(
name: "IX_FeedBookmark_MemberID_CreatedAt",
table: "FeedBookmark",
columns: new[] { "MemberID", "CreatedAt" },
descending: new[] { false, true });
migrationBuilder.CreateIndex(
name: "IX_FeedComment_FeedPostID",
table: "FeedComment",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedComment_FeedPostID_IsDeleted_ParentID_CreatedAt",
table: "FeedComment",
columns: new[] { "FeedPostID", "IsDeleted", "ParentID", "CreatedAt" },
descending: new[] { false, false, false, true });
migrationBuilder.CreateIndex(
name: "IX_FeedComment_FeedPostID_IsDeleted_ParentID_Score_ID",
table: "FeedComment",
columns: new[] { "FeedPostID", "IsDeleted", "ParentID", "Score", "ID" },
descending: new[] { false, false, false, true, true });
migrationBuilder.CreateIndex(
name: "IX_FeedComment_FeedPostID_MemberID_IsDeleted_ParentID_CreatedAt",
table: "FeedComment",
columns: new[] { "FeedPostID", "MemberID", "IsDeleted", "ParentID", "CreatedAt" },
descending: new[] { false, false, false, false, true });
migrationBuilder.CreateIndex(
name: "IX_FeedComment_MemberID",
table: "FeedComment",
column: "MemberID");
migrationBuilder.CreateIndex(
name: "IX_FeedComment_ParentID",
table: "FeedComment",
column: "ParentID");
migrationBuilder.CreateIndex(
name: "IX_FeedCommentLike_FeedCommentID",
table: "FeedCommentLike",
column: "FeedCommentID");
migrationBuilder.CreateIndex(
name: "IX_FeedCommentLike_FeedCommentID_MemberID",
table: "FeedCommentLike",
columns: new[] { "FeedCommentID", "MemberID" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedCommentLike_FeedPostID",
table: "FeedCommentLike",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedCommentLike_MemberID",
table: "FeedCommentLike",
column: "MemberID");
migrationBuilder.CreateIndex(
name: "IX_FeedLike_FeedPostID",
table: "FeedLike",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedLike_FeedPostID_MemberID",
table: "FeedLike",
columns: new[] { "FeedPostID", "MemberID" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedLike_MemberID",
table: "FeedLike",
column: "MemberID");
migrationBuilder.CreateIndex(
name: "IX_FeedPost_CreatedAt",
table: "FeedPost",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_FeedPost_IsDeleted",
table: "FeedPost",
column: "IsDeleted");
migrationBuilder.CreateIndex(
name: "IX_FeedPost_IsDeleted_CreatedAt",
table: "FeedPost",
columns: new[] { "IsDeleted", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_FeedPost_IsDeleted_Likes_Comments_Views",
table: "FeedPost",
columns: new[] { "IsDeleted", "Likes", "Comments", "Views" });
migrationBuilder.CreateIndex(
name: "IX_FeedPost_MemberID",
table: "FeedPost",
column: "MemberID");
migrationBuilder.CreateIndex(
name: "IX_FeedPost_MemberID_IsDeleted_CreatedAt",
table: "FeedPost",
columns: new[] { "MemberID", "IsDeleted", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_FeedPostImage_FeedPostID",
table: "FeedPostImage",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedPostImage_FeedPostID_HashedName",
table: "FeedPostImage",
columns: new[] { "FeedPostID", "HashedName" });
migrationBuilder.CreateIndex(
name: "IX_FeedPostImage_FeedPostID_IsDisabled",
table: "FeedPostImage",
columns: new[] { "FeedPostID", "IsDisabled" });
migrationBuilder.CreateIndex(
name: "IX_FeedPostImage_UUID",
table: "FeedPostImage",
column: "UUID",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedPostMedia_FeedPostID",
table: "FeedPostMedia",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedPostMention_FeedPostID",
table: "FeedPostMention",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedPostMention_FeedPostID_MemberID",
table: "FeedPostMention",
columns: new[] { "FeedPostID", "MemberID" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedPostMention_MemberID",
table: "FeedPostMention",
column: "MemberID");
migrationBuilder.CreateIndex(
name: "IX_FeedPostMention_MemberID_CreatedAt",
table: "FeedPostMention",
columns: new[] { "MemberID", "CreatedAt" },
descending: new[] { false, true });
migrationBuilder.CreateIndex(
name: "IX_FeedPostTag_FeedPostID",
table: "FeedPostTag",
column: "FeedPostID");
migrationBuilder.CreateIndex(
name: "IX_FeedPostTag_FeedPostID_FeedTagID",
table: "FeedPostTag",
columns: new[] { "FeedPostID", "FeedTagID" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedPostTag_FeedTagID",
table: "FeedPostTag",
column: "FeedTagID");
migrationBuilder.CreateIndex(
name: "IX_FeedPostTag_FeedTagID_CreatedAt",
table: "FeedPostTag",
columns: new[] { "FeedTagID", "CreatedAt" },
descending: new[] { false, true });
migrationBuilder.CreateIndex(
name: "IX_FeedTag_Name",
table: "FeedTag",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedTag_Slug",
table: "FeedTag",
column: "Slug",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_FeedTag_UsageCount",
table: "FeedTag",
column: "UsageCount",
descending: new bool[0]);
}
///
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "FeedBookmark");
migrationBuilder.DropTable(
name: "FeedCommentLike");
migrationBuilder.DropTable(
name: "FeedLike");
migrationBuilder.DropTable(
name: "FeedPostImage");
migrationBuilder.DropTable(
name: "FeedPostMedia");
migrationBuilder.DropTable(
name: "FeedPostMention");
migrationBuilder.DropTable(
name: "FeedPostTag");
migrationBuilder.DropTable(
name: "FeedComment");
migrationBuilder.DropTable(
name: "FeedTag");
migrationBuilder.DropTable(
name: "FeedPost");
}
}