using BTCPayServer.Data; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace BTCPayServer.Migrations { [DbContext(typeof(ApplicationDbContext))] [Migration("20260114053517_StoreScopedLabels")] public partial class StoreScopedLabels : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "store_labels", columns: table => new { store_id = table.Column(type: "text", nullable: false), id = table.Column(type: "text", nullable: false), type = table.Column(type: "text", nullable: false), text = table.Column(type: "text", nullable: false), color = table.Column(type: "text", nullable: true), }, constraints: table => { table.PrimaryKey("PK_store_labels", x => new { x.store_id, x.id }); }); migrationBuilder.Sql( """ CREATE UNIQUE INDEX "IX_store_labels_store_id_type_text_lower" ON store_labels (store_id, type, lower(text)); """); migrationBuilder.CreateTable( name: "store_label_links", columns: table => new { store_id = table.Column(type: "text", nullable: false), store_label_id = table.Column(type: "text", nullable: false), object_id = table.Column(type: "text", nullable: false), }, constraints: table => { table.PrimaryKey("PK_store_label_links", x => new { x.store_id, x.store_label_id, x.object_id }); table.ForeignKey( name: "FK_store_label_links_store_labels_store_id_store_label_id", columns: x => new { x.store_id, x.store_label_id }, principalTable: "store_labels", principalColumns: new[] { "store_id", "id" }, onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_store_label_links_store_id_object_id", table: "store_label_links", columns: new[] { "store_id", "object_id" }); // Copy Payment Request label objects (label metadata) into StoreLabels migrationBuilder.Sql(@" WITH pr_links AS ( SELECT DISTINCT wol.""WalletId"" AS ""WalletId"", wol.""AId"" AS ""LabelText"", wol.""BId"" AS ""PaymentRequestId"" FROM ""WalletObjectLinks"" wol WHERE wol.""AType"" = 'label' AND wol.""BType"" = 'payment-request' ), pr_labels AS ( SELECT DISTINCT pr.""StoreDataId"" AS ""StoreId"", pl.""LabelText"", wo.""Data"" AS ""LabelData"" FROM pr_links pl INNER JOIN ""PaymentRequests"" pr ON pr.""Id"" = pl.""PaymentRequestId"" INNER JOIN ""WalletObjects"" wo ON wo.""WalletId"" = pl.""WalletId"" AND wo.""Type"" = 'label' AND wo.""Id"" = pl.""LabelText"" ) INSERT INTO store_labels (store_id, id, type, text, color) SELECT ""StoreId"", gen_random_uuid()::text, 'payment-request', ""LabelText"", (""LabelData""::jsonb ->> 'color') FROM pr_labels ON CONFLICT (store_id, type, (lower(text))) DO NOTHING; "); // Copy Payment Request label links into StoreLabelLinks migrationBuilder.Sql(@" WITH pr_links AS ( SELECT DISTINCT pr.""StoreDataId"" AS ""StoreId"", wol.""AId"" AS ""LabelText"", wol.""BId"" AS ""ObjectId"" FROM ""WalletObjectLinks"" wol INNER JOIN ""PaymentRequests"" pr ON pr.""Id"" = wol.""BId"" WHERE wol.""AType"" = 'label' AND wol.""BType"" = 'payment-request' ) INSERT INTO store_label_links (store_id, store_label_id, object_id) SELECT pl.""StoreId"", sl.id AS store_label_id, pl.""ObjectId"" FROM pr_links pl INNER JOIN store_labels sl ON sl.store_id = pl.""StoreId"" AND sl.type = 'payment-request' AND sl.text = pl.""LabelText"" ON CONFLICT (store_id, store_label_id, object_id) DO NOTHING; "); // Remove the Payment Request label links from the wallet graph migrationBuilder.Sql(@" DELETE FROM ""WalletObjectLinks"" wol WHERE wol.""AType"" = 'label' AND wol.""BType"" = 'payment-request'; "); // Remove unlinked Labels from WalletObjects migrationBuilder.Sql(@" WITH pr_wallets AS ( SELECT DISTINCT wo.""WalletId"" FROM ""WalletObjects"" wo WHERE wo.""Type"" = 'payment-request' ) DELETE FROM ""WalletObjects"" wo WHERE wo.""Type"" = 'label' AND wo.""WalletId"" IN (SELECT ""WalletId"" FROM pr_wallets) AND NOT EXISTS ( SELECT 1 FROM ""WalletObjectLinks"" wol WHERE wol.""WalletId"" = wo.""WalletId"" AND ( (wol.""AType"" = 'label' AND wol.""AId"" = wo.""Id"") OR (wol.""BType"" = 'label' AND wol.""BId"" = wo.""Id"") ) ); "); } /// protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "store_label_links"); migrationBuilder.DropTable( name: "store_labels"); } } }