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"); } }