From f237851e4265b5abab5cf6f55cb35972a14a6a6e Mon Sep 17 00:00:00 2001 From: gamer147 Date: Wed, 27 May 2026 22:09:45 -0400 Subject: [PATCH] feat(sleeve): shop catalog + /sleeve/{info,buy} endpoints Schema: SleeveShopSeries -> SleeveShopProducts -> Rewards (owned). Migration AddSleeveShop creates 3 tables with FK cascade. Importer mirrors BuildDeck pattern: find-or-create per series/product, rewards replaced wholesale on rerun (owned collection). 10 series, 270 products imported from seeds/sleeve-shop.json. Controller: - /sleeve/info returns wire-faithful dict-keyed shape ({sleeve_list: {: {product_info: {: ...}}}}). is_purchased_product derived from viewer.Sleeves.Contains(sleeve_id). - /sleeve/buy: sales_type 0=free / 1=crystal / 2=rupy / 3=ticket(501). Validates series_product mismatch, currency, already-purchased. Currency debited with post-state-total reward_list entry; cosmetic grants dispatched through RewardGrantService.ApplyAsync (covers sleeve + emblem bundled grants per product). 476 tests pass (was 466; +10 sleeve tests). Co-Authored-By: Claude Opus 4.7 --- SVSim.Bootstrap/Data/seeds/sleeve-shop.json | 5918 +++++++++++++++++ .../Importers/SleeveShopImporter.cs | 89 + SVSim.Bootstrap/Models/Seed/SleeveShopSeed.cs | 27 + SVSim.Bootstrap/Program.cs | 1 + .../20260528015716_AddSleeveShop.Designer.cs | 3383 ++++++++++ .../20260528015716_AddSleeveShop.cs | 96 + .../Migrations/SVSimDbContextModelSnapshot.cs | 105 + .../Models/SleeveShopProductEntry.cs | 32 + .../Models/SleeveShopProductRewardEntry.cs | 17 + .../Models/SleeveShopSeriesEntry.cs | 16 + SVSim.Database/SVSimDbContext.cs | 10 + .../Controllers/SleeveController.cs | 189 + .../Dtos/Requests/Sleeve/SleeveBuyRequest.cs | 25 + .../Responses/Sleeve/SleeveBuyResponse.cs | 19 + .../Responses/Sleeve/SleeveInfoResponse.cs | 84 + .../Controllers/SleeveControllerTests.cs | 217 + .../Importers/SleeveShopImporterTests.cs | 87 + .../Infrastructure/SVSimTestFactory.cs | 1 + 18 files changed, 10316 insertions(+) create mode 100644 SVSim.Bootstrap/Data/seeds/sleeve-shop.json create mode 100644 SVSim.Bootstrap/Importers/SleeveShopImporter.cs create mode 100644 SVSim.Bootstrap/Models/Seed/SleeveShopSeed.cs create mode 100644 SVSim.Database/Migrations/20260528015716_AddSleeveShop.Designer.cs create mode 100644 SVSim.Database/Migrations/20260528015716_AddSleeveShop.cs create mode 100644 SVSim.Database/Models/SleeveShopProductEntry.cs create mode 100644 SVSim.Database/Models/SleeveShopProductRewardEntry.cs create mode 100644 SVSim.Database/Models/SleeveShopSeriesEntry.cs create mode 100644 SVSim.EmulatedEntrypoint/Controllers/SleeveController.cs create mode 100644 SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Sleeve/SleeveBuyRequest.cs create mode 100644 SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveBuyResponse.cs create mode 100644 SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveInfoResponse.cs create mode 100644 SVSim.UnitTests/Controllers/SleeveControllerTests.cs create mode 100644 SVSim.UnitTests/Importers/SleeveShopImporterTests.cs diff --git a/SVSim.Bootstrap/Data/seeds/sleeve-shop.json b/SVSim.Bootstrap/Data/seeds/sleeve-shop.json new file mode 100644 index 0000000..31695ef --- /dev/null +++ b/SVSim.Bootstrap/Data/seeds/sleeve-shop.json @@ -0,0 +1,5918 @@ +[ + { + "series_id": 3001, + "is_new": false, + "products": [ + { + "product_id": 300101, + "name_key": "sleeve_1", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3001011, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3001012, + "reward_number": 1 + } + ] + }, + { + "product_id": 300102, + "name_key": "sleeve_2", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3001021, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3001022, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001023, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3004, + "is_new": false, + "products": [ + { + "product_id": 300401, + "name_key": "sleeve_7", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3004011, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004012, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3004013, + "reward_number": 1 + } + ] + }, + { + "product_id": 300402, + "name_key": "sleeve_8", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3004021, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004022, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3004023, + "reward_number": 1 + } + ] + }, + { + "product_id": 300403, + "name_key": "sleeve_97", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000011, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004031, + "reward_number": 1 + } + ] + }, + { + "product_id": 300404, + "name_key": "sleeve_98", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000012, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004032, + "reward_number": 1 + } + ] + }, + { + "product_id": 300405, + "name_key": "sleeve_99", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000013, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004033, + "reward_number": 1 + } + ] + }, + { + "product_id": 300406, + "name_key": "sleeve_100", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000014, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004034, + "reward_number": 1 + } + ] + }, + { + "product_id": 300407, + "name_key": "sleeve_101", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000015, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004035, + "reward_number": 1 + } + ] + }, + { + "product_id": 300408, + "name_key": "sleeve_102", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000016, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004036, + "reward_number": 1 + } + ] + }, + { + "product_id": 300409, + "name_key": "sleeve_103", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000017, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004037, + "reward_number": 1 + } + ] + }, + { + "product_id": 300410, + "name_key": "sleeve_104", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000018, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004038, + "reward_number": 1 + } + ] + }, + { + "product_id": 300411, + "name_key": "sleeve_105", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000019, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004039, + "reward_number": 1 + } + ] + }, + { + "product_id": 300412, + "name_key": "sleeve_106", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3004040, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3010, + "is_new": false, + "products": [ + { + "product_id": 301001, + "name_key": "sleeve_11", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 101021010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 101021010, + "reward_number": 1 + } + ] + }, + { + "product_id": 301002, + "name_key": "sleeve_12", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 101031010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 101031010, + "reward_number": 1 + } + ] + }, + { + "product_id": 301003, + "name_key": "sleeve_13", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900311010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900311010, + "reward_number": 1 + } + ] + }, + { + "product_id": 301004, + "name_key": "sleeve_14", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900241010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900241010, + "reward_number": 1 + } + ] + }, + { + "product_id": 301005, + "name_key": "sleeve_15", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 102732020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 102732020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301006, + "name_key": "sleeve_16", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 102131030, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 102131030, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3012, + "is_new": false, + "products": [ + { + "product_id": 301201, + "name_key": "sleeve_20", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 103521040, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 103521040, + "reward_number": 1 + } + ] + }, + { + "product_id": 301202, + "name_key": "sleeve_21", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 103121030, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 103121030, + "reward_number": 1 + } + ] + }, + { + "product_id": 301203, + "name_key": "sleeve_22", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 103011060, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 103011060, + "reward_number": 1 + } + ] + }, + { + "product_id": 301204, + "name_key": "sleeve_23", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 103021020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 103021020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301205, + "name_key": "sleeve_24", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 103631030, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 103631030, + "reward_number": 1 + } + ] + }, + { + "product_id": 301206, + "name_key": "sleeve_25", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 103431030, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 103431030, + "reward_number": 1 + } + ] + }, + { + "product_id": 301207, + "name_key": "sleeve_26", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 104621020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 104621020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301208, + "name_key": "sleeve_27", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 104531020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 104531020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301209, + "name_key": "sleeve_28", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 104721020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 104721020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301210, + "name_key": "sleeve_29", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 104231030, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 104231030, + "reward_number": 1 + } + ] + }, + { + "product_id": 301211, + "name_key": "sleeve_30", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 104321020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 104321020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301212, + "name_key": "sleeve_31", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 104431030, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 104431030, + "reward_number": 1 + } + ] + }, + { + "product_id": 301213, + "name_key": "sleeve_32", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1054210300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1054210300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301214, + "name_key": "sleeve_33", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1051310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1051310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301215, + "name_key": "sleeve_34", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1056210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1056210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301216, + "name_key": "sleeve_35", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1052210300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1052210300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301217, + "name_key": "sleeve_36", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1055310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1055310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301218, + "name_key": "sleeve_37", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1050310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1050310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301219, + "name_key": "sleeve_40", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1063310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1063310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301220, + "name_key": "sleeve_41", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1067310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1067310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301221, + "name_key": "sleeve_42", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1064110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1064110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301222, + "name_key": "sleeve_43", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1061310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1061310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301223, + "name_key": "sleeve_44", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1065310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1065310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301224, + "name_key": "sleeve_45", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1066210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1066210200, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3013, + "is_new": false, + "products": [ + { + "product_id": 301301, + "name_key": "sleeve_38", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3030002, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400001003, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400001002, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 7, + "reward_detail_id": 400001004, + "reward_number": 1 + } + ] + }, + { + "product_id": 301302, + "name_key": "sleeve_39", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400001005, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400001007, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400001006, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 7, + "reward_detail_id": 400001008, + "reward_number": 1 + } + ] + }, + { + "product_id": 301303, + "name_key": "sleeve_119", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3030001, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400001001, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3014, + "is_new": false, + "products": [ + { + "product_id": 301401, + "name_key": "sleeve_46", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1070210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1070210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301402, + "name_key": "sleeve_47", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1078210600, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1078210600, + "reward_number": 1 + } + ] + }, + { + "product_id": 301403, + "name_key": "sleeve_48", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1072310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1072310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301404, + "name_key": "sleeve_49", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1071310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1071310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301405, + "name_key": "sleeve_50", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1074210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1074210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301406, + "name_key": "sleeve_51", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1073310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1073310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301407, + "name_key": "sleeve_52", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1080110200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1080110200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301408, + "name_key": "sleeve_53", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1088310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1088310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301409, + "name_key": "sleeve_54", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1085310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1085310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301410, + "name_key": "sleeve_55", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1087320100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1087320100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301411, + "name_key": "sleeve_56", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1086210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1086210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301412, + "name_key": "sleeve_57", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1082210300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1082210300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301413, + "name_key": "sleeve_58", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1098210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1098210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301414, + "name_key": "sleeve_59", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1091210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1091210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301415, + "name_key": "sleeve_60", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1097310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1097310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301416, + "name_key": "sleeve_61", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900231040, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 900231050, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 900231060, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 7, + "reward_detail_id": 900231070, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 6, + "reward_detail_id": 1092340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301417, + "name_key": "sleeve_62", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1094310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1094310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301418, + "name_key": "sleeve_63", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1093210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1093210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301419, + "name_key": "sleeve_64", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1106110200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1106110200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301420, + "name_key": "sleeve_65", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1101210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1101210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301421, + "name_key": "sleeve_66", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1103110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1103110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301422, + "name_key": "sleeve_67", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1107310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1107310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301423, + "name_key": "sleeve_68", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1102310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1102310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301424, + "name_key": "sleeve_69", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1105310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1105310100, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3015, + "is_new": false, + "products": [ + { + "product_id": 301425, + "name_key": "sleeve_70", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1118310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1118310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301426, + "name_key": "sleeve_71", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1110310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1110310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301427, + "name_key": "sleeve_72", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1117210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1117210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301428, + "name_key": "sleeve_73", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1116110300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1116110300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301429, + "name_key": "sleeve_74", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1114110300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1114110300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301430, + "name_key": "sleeve_75", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1113310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1113310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301431, + "name_key": "sleeve_79", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1124110300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1124110300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301432, + "name_key": "sleeve_80", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1125210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1125210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301433, + "name_key": "sleeve_81", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1122310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1122310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301434, + "name_key": "sleeve_82", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1128310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1128310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301435, + "name_key": "sleeve_83", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1120310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1120310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301436, + "name_key": "sleeve_84", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1121310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1121310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301501, + "name_key": "sleeve_85", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1138110200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1138110200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301502, + "name_key": "sleeve_86", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1130310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1130310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301503, + "name_key": "sleeve_87", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1137110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1137110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301504, + "name_key": "sleeve_88", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1136310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1136310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301505, + "name_key": "sleeve_89", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1131310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1131310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301506, + "name_key": "sleeve_90", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1133310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1133310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301507, + "name_key": "sleeve_91", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900711140, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900711140, + "reward_number": 1 + } + ] + }, + { + "product_id": 301508, + "name_key": "sleeve_92", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1142310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1142310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301509, + "name_key": "sleeve_93", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1143140100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1143140100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301510, + "name_key": "sleeve_94", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1144340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1144340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301511, + "name_key": "sleeve_95", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1140340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1140340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301512, + "name_key": "sleeve_96", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1148310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1148310200, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3016, + "is_new": false, + "products": [ + { + "product_id": 301601, + "name_key": "sleeve_107", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1152310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1152310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301602, + "name_key": "sleeve_108", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1157310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1157310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301603, + "name_key": "sleeve_109", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1155310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1155310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301604, + "name_key": "sleeve_110", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1156130100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1156130100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301605, + "name_key": "sleeve_111", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1150310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1150310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301606, + "name_key": "sleeve_112", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1153140100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1153140100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301607, + "name_key": "sleeve_113", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1164110200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1164110200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301608, + "name_key": "sleeve_114", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1160310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1160310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301609, + "name_key": "sleeve_115", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1161110300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1161110300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301610, + "name_key": "sleeve_116", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1165310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1165310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301611, + "name_key": "sleeve_117", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1167310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1167310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301612, + "name_key": "sleeve_118", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1166310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1166310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301613, + "name_key": "sleeve_132", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1172210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1172210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301614, + "name_key": "sleeve_133", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1177310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1177310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301615, + "name_key": "sleeve_134", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1174210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1174210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301616, + "name_key": "sleeve_135", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1176310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1176310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301617, + "name_key": "sleeve_136", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1175340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1175340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301618, + "name_key": "sleeve_137", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1170310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1170310100, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3017, + "is_new": false, + "products": [ + { + "product_id": 301701, + "name_key": "sleeve_120", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003202, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057002, + "reward_number": 1 + } + ] + }, + { + "product_id": 301702, + "name_key": "sleeve_121", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003203, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057003, + "reward_number": 1 + } + ] + }, + { + "product_id": 301703, + "name_key": "sleeve_122", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003204, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057004, + "reward_number": 1 + } + ] + }, + { + "product_id": 301704, + "name_key": "sleeve_123", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003205, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057005, + "reward_number": 1 + } + ] + }, + { + "product_id": 301705, + "name_key": "sleeve_124", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003206, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057006, + "reward_number": 1 + } + ] + }, + { + "product_id": 301706, + "name_key": "sleeve_125", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003207, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057007, + "reward_number": 1 + } + ] + }, + { + "product_id": 301707, + "name_key": "sleeve_126", + "price_crystal": 0, + "price_rupy": 0, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400003208, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3057008, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 3019, + "is_new": false, + "products": [ + { + "product_id": 301901, + "name_key": "sleeve_138", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1183310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1183310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301902, + "name_key": "sleeve_139", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1187320100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1187320100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301903, + "name_key": "sleeve_140", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1186310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1186310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301904, + "name_key": "sleeve_141", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1181310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1181310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301905, + "name_key": "sleeve_142", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1188310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1188310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301906, + "name_key": "sleeve_143", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1184340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1184340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301907, + "name_key": "sleeve_144", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1190310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1190310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301908, + "name_key": "sleeve_145", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1194310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1194310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301909, + "name_key": "sleeve_146", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1197210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1197210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301910, + "name_key": "sleeve_147", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1191310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1191310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301911, + "name_key": "sleeve_148", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1196310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1196310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301912, + "name_key": "sleeve_149", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1195310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1195310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301913, + "name_key": "sleeve_150", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 713424010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 713424010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 713424010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 713424011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301914, + "name_key": "sleeve_151", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 713531010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 713531010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 713531010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 713531011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301915, + "name_key": "sleeve_152", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1200310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1200310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301916, + "name_key": "sleeve_153", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1207330100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1207330100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301917, + "name_key": "sleeve_154", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1208310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1208310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301918, + "name_key": "sleeve_155", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1205310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1205310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301919, + "name_key": "sleeve_156", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1204310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1204310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301920, + "name_key": "sleeve_157", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1202310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1202310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301921, + "name_key": "sleeve_158", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715314010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715314010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715314010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715314011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301922, + "name_key": "sleeve_159", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715011010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715011010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715011010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715011011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301923, + "name_key": "sleeve_160", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1215240150, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1215240160, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1215240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301924, + "name_key": "sleeve_161", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1218240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1218240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301925, + "name_key": "sleeve_162", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1214240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1214240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301926, + "name_key": "sleeve_163", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1215310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1215310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301927, + "name_key": "sleeve_164", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900242020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900242020, + "reward_number": 1 + } + ] + }, + { + "product_id": 301928, + "name_key": "sleeve_165", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900644060, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900644060, + "reward_number": 1 + } + ] + }, + { + "product_id": 301929, + "name_key": "sleeve_166", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715031010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715031010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715031010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715031011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301930, + "name_key": "sleeve_167", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715611010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715611010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715611010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715611011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301931, + "name_key": "sleeve_168", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1225310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1225310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301932, + "name_key": "sleeve_169", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1222310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1222310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301933, + "name_key": "sleeve_170", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1224310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1224310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301934, + "name_key": "sleeve_171", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1227310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1227310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301935, + "name_key": "sleeve_172", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1220310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1220310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301936, + "name_key": "sleeve_173", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1226310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1226310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301937, + "name_key": "sleeve_174", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715721010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715721010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715721010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715721011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301938, + "name_key": "sleeve_175", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715221010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715221010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715221010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715221011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301939, + "name_key": "sleeve_176", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1231240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1231240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301940, + "name_key": "sleeve_177", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1236240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1236240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301941, + "name_key": "sleeve_178", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1230340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1230340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301942, + "name_key": "sleeve_179", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1232340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1232340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301943, + "name_key": "sleeve_180", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1233340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1233340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301944, + "name_key": "sleeve_181", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1238340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1238340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301945, + "name_key": "sleeve_182", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715031020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715031020, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715031020, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715031021, + "reward_number": 3 + } + ] + }, + { + "product_id": 301946, + "name_key": "sleeve_183", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 715121010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 715121010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 715121010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 715121011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301947, + "name_key": "sleeve_184", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1241210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1241210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301948, + "name_key": "sleeve_185", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1248340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1248340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301949, + "name_key": "sleeve_186", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1246210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1246210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301950, + "name_key": "sleeve_187", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1242340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1242340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301951, + "name_key": "sleeve_188", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1240310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1240310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301952, + "name_key": "sleeve_189", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1243310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1243310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301953, + "name_key": "sleeve_190", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717331010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717331010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717331010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717331011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301954, + "name_key": "sleeve_191", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717811010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717811010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717811010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717811011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301955, + "name_key": "sleeve_192", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1250210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1250210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301956, + "name_key": "sleeve_193", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1252310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1252310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301957, + "name_key": "sleeve_194", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1253210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1253210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301958, + "name_key": "sleeve_195", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1257210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1257210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301959, + "name_key": "sleeve_196", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1254310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1254310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301960, + "name_key": "sleeve_197", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1250310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1250310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301961, + "name_key": "sleeve_198", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717221020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717221020, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717221020, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717221021, + "reward_number": 3 + } + ] + }, + { + "product_id": 301962, + "name_key": "sleeve_199", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717131010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717131010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717131010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717131011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301963, + "name_key": "sleeve_200", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1261310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1261310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301964, + "name_key": "sleeve_201", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1267110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1267110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301965, + "name_key": "sleeve_202", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1263210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1263210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301966, + "name_key": "sleeve_203", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1265310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1265310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301967, + "name_key": "sleeve_204", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1260310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1260310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301968, + "name_key": "sleeve_205", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1268310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1268310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301969, + "name_key": "sleeve_206", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717024010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717024010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717024010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717024011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301970, + "name_key": "sleeve_207", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717811020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717811020, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717811020, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717811021, + "reward_number": 3 + } + ] + }, + { + "product_id": 301971, + "name_key": "sleeve_208", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1272210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1272210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301972, + "name_key": "sleeve_209", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1277310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1277310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301973, + "name_key": "sleeve_210", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1278110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1278110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301974, + "name_key": "sleeve_211", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1274310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1274310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301975, + "name_key": "sleeve_212", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1270340150, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1270340160, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1270340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301976, + "name_key": "sleeve_213", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1276340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1276340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301977, + "name_key": "sleeve_214", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717011010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 717011010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 717011010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717011011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301978, + "name_key": "sleeve_215", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 717521015, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 717521016, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717521010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 717521010, + "reward_number": 3 + }, + { + "order_index": 4, + "reward_type": 5, + "reward_detail_id": 717521011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301979, + "name_key": "sleeve_216", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1288310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1288310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301980, + "name_key": "sleeve_217", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1280310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1280310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301981, + "name_key": "sleeve_218", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1281310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1281310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301982, + "name_key": "sleeve_219", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1286240150, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1286240160, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1286240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301983, + "name_key": "sleeve_220", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1285340100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1285340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301984, + "name_key": "sleeve_221", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1287310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1287310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301985, + "name_key": "sleeve_222", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720411010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720411010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720411010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720411011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301986, + "name_key": "sleeve_223", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720314010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720314010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720314010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720314011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301987, + "name_key": "sleeve_224", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1290310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1290310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301988, + "name_key": "sleeve_225", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1291310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1291310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301989, + "name_key": "sleeve_226", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1298210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1298210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301990, + "name_key": "sleeve_227", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1292240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1292240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301991, + "name_key": "sleeve_228", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1297310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1297310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301992, + "name_key": "sleeve_229", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1293310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1293310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301993, + "name_key": "sleeve_230", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720031010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720031010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720031010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720031011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301994, + "name_key": "sleeve_231", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720614010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720614010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720614010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720614011, + "reward_number": 3 + } + ] + }, + { + "product_id": 301995, + "name_key": "sleeve_232", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1308110300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1308110300, + "reward_number": 1 + } + ] + }, + { + "product_id": 301996, + "name_key": "sleeve_233", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1303110200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1303110200, + "reward_number": 1 + } + ] + }, + { + "product_id": 301997, + "name_key": "sleeve_234", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1300310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1300310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301998, + "name_key": "sleeve_235", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1302310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1302310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 301999, + "name_key": "sleeve_236", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1306310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1306310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302000, + "name_key": "sleeve_237", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1304310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1304310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302001, + "name_key": "sleeve_238", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720713010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720713010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720713010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720713011, + "reward_number": 3 + } + ] + }, + { + "product_id": 302002, + "name_key": "sleeve_239", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720321010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720321010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720321010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720321011, + "reward_number": 3 + } + ] + }, + { + "product_id": 302003, + "name_key": "sleeve_240", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1316110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1316110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302004, + "name_key": "sleeve_241", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1311210300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1311210300, + "reward_number": 1 + } + ] + }, + { + "product_id": 302005, + "name_key": "sleeve_242", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1313240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1313240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302006, + "name_key": "sleeve_243", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1314210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1314210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302007, + "name_key": "sleeve_244", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1315310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1315310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302008, + "name_key": "sleeve_245", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1317310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1317310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 302009, + "name_key": "sleeve_246", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720211010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720211010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720211010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720211011, + "reward_number": 3 + } + ] + }, + { + "product_id": 302010, + "name_key": "sleeve_247", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 720421010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 720421010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 720421010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 720421011, + "reward_number": 3 + } + ] + }, + { + "product_id": 302011, + "name_key": "sleeve_248", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1322210200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1322210200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302012, + "name_key": "sleeve_249", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900534060, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900534060, + "reward_number": 1 + } + ] + }, + { + "product_id": 302013, + "name_key": "sleeve_250", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1326340150, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1326340160, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1326340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302014, + "name_key": "sleeve_251", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1321340150, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1321340160, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1321340100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302015, + "name_key": "sleeve_252", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1323310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1323310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302016, + "name_key": "sleeve_253", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900834120, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900834120, + "reward_number": 1 + } + ] + }, + { + "product_id": 302017, + "name_key": "sleeve_254", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 722723010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 722723010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 722723010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 722723011, + "reward_number": 3 + } + ] + }, + { + "product_id": 302018, + "name_key": "sleeve_255", + "price_crystal": 500, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 722031010, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 722031010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 5, + "reward_detail_id": 722031010, + "reward_number": 3 + }, + { + "order_index": 3, + "reward_type": 5, + "reward_detail_id": 722031011, + "reward_number": 3 + } + ] + }, + { + "product_id": 302019, + "name_key": "sleeve_256", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1128210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1128210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302020, + "name_key": "sleeve_257", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1266310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1266310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 302021, + "name_key": "sleeve_258", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1233240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1233240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302022, + "name_key": "sleeve_259", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1111310350, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1111310360, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1111310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 302023, + "name_key": "sleeve_260", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1185240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1185240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302024, + "name_key": "sleeve_261", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1264310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1264310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 302025, + "name_key": "sleeve_262", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 200000200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302026, + "name_key": "sleeve_263", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000255, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000256, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000250, + "reward_number": 1 + } + ] + }, + { + "product_id": 302027, + "name_key": "sleeve_264", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1257210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1257210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302028, + "name_key": "sleeve_265", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1078310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1078310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302029, + "name_key": "sleeve_266", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1153210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1153210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302030, + "name_key": "sleeve_267", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1122240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1122240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302031, + "name_key": "sleeve_268", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1140310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1140310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302032, + "name_key": "sleeve_269", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1094310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1094310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302033, + "name_key": "sleeve_270", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000270, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 200000270, + "reward_number": 1 + } + ] + }, + { + "product_id": 302034, + "name_key": "sleeve_271", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000170, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 200000170, + "reward_number": 1 + } + ] + }, + { + "product_id": 302035, + "name_key": "sleeve_272", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1213240100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1213240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302036, + "name_key": "sleeve_273", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1235310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1235310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302037, + "name_key": "sleeve_274", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1256210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1256210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302038, + "name_key": "sleeve_275", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1267210100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1267210100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302039, + "name_key": "sleeve_276", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1182310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1182310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302040, + "name_key": "sleeve_277", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1221310100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1221310100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302041, + "name_key": "sleeve_278", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000290, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 200000290, + "reward_number": 1 + } + ] + }, + { + "product_id": 302042, + "name_key": "sleeve_279", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000325, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000326, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000320, + "reward_number": 1 + } + ] + }, + { + "product_id": 302043, + "name_key": "sleeve_280", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1140110100, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1140110100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302044, + "name_key": "sleeve_281", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1213310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1213310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302045, + "name_key": "sleeve_282", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1296240500, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 1296240600, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1296240100, + "reward_number": 1 + } + ] + }, + { + "product_id": 302046, + "name_key": "sleeve_283", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1225310200, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1225310200, + "reward_number": 1 + } + ] + }, + { + "product_id": 302047, + "name_key": "sleeve_284", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 900031020, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 900031020, + "reward_number": 1 + } + ] + }, + { + "product_id": 302048, + "name_key": "sleeve_285", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 1131310300, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 1131310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 302049, + "name_key": "sleeve_286", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000350, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 200000350, + "reward_number": 1 + } + ] + }, + { + "product_id": 302050, + "name_key": "sleeve_287", + "price_crystal": 400, + "price_rupy": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 200000220, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 200000220, + "reward_number": 1 + } + ] + } + ] + } +] diff --git a/SVSim.Bootstrap/Importers/SleeveShopImporter.cs b/SVSim.Bootstrap/Importers/SleeveShopImporter.cs new file mode 100644 index 0000000..f2b9428 --- /dev/null +++ b/SVSim.Bootstrap/Importers/SleeveShopImporter.cs @@ -0,0 +1,89 @@ +using Microsoft.EntityFrameworkCore; +using SVSim.Bootstrap.Models.Seed; +using SVSim.Database; +using SVSim.Database.Models; + +namespace SVSim.Bootstrap.Importers; + +/// +/// Idempotent upsert of the sleeve-shop catalog from seeds/sleeve-shop.json. +/// Source is the wire /sleeve/info response, extracted via +/// data_dumps/extract/extract-sleeve-shop.py. Mirror of the BuildDeck importer pattern. +/// Rows missing from the seed are LEFT INTACT (so manual test fixtures survive re-runs). +/// +public class SleeveShopImporter +{ + public async Task ImportAsync(SVSimDbContext context, string seedDir) + { + string path = Path.Combine(seedDir, "sleeve-shop.json"); + var seed = SeedLoader.LoadList(path); + if (seed.Count == 0) + { + Console.WriteLine("[SleeveShopImporter] No seed rows; skipping."); + return 0; + } + + var existingSeries = await context.SleeveShopSeries + .Include(s => s.Products).ThenInclude(p => p.Rewards) + .ToDictionaryAsync(s => s.Id); + + int createdSeries = 0, updatedSeries = 0, createdProducts = 0, updatedProducts = 0; + + foreach (var s in seed) + { + if (s.SeriesId == 0) continue; + + if (!existingSeries.TryGetValue(s.SeriesId, out var series)) + { + series = new SleeveShopSeriesEntry { Id = s.SeriesId }; + context.SleeveShopSeries.Add(series); + existingSeries[s.SeriesId] = series; + createdSeries++; + } + else updatedSeries++; + + series.IsNew = s.IsNew; + series.IsEnabled = true; + + var existingProducts = series.Products.ToDictionary(p => p.Id); + foreach (var p in s.Products) + { + if (p.ProductId == 0) continue; + + if (!existingProducts.TryGetValue(p.ProductId, out var product)) + { + product = new SleeveShopProductEntry { Id = p.ProductId }; + series.Products.Add(product); + createdProducts++; + } + else updatedProducts++; + + product.SeriesId = s.SeriesId; + product.NameKey = p.NameKey; + product.PriceCrystal = p.PriceCrystal; + product.PriceRupy = p.PriceRupy; + product.IsEnabled = true; + + // Rewards: replace wholesale (owned collection — EF will issue DELETE+INSERT + // anyway, and the wire shape is canonical per re-extract). + product.Rewards.Clear(); + foreach (var r in p.Rewards.OrderBy(r => r.OrderIndex)) + { + product.Rewards.Add(new SleeveShopProductRewardEntry + { + OrderIndex = r.OrderIndex, + RewardType = r.RewardType, + RewardDetailId = r.RewardDetailId, + RewardNumber = r.RewardNumber, + }); + } + } + } + + await context.SaveChangesAsync(); + Console.WriteLine( + $"[SleeveShopImporter] series +{createdSeries}/~{updatedSeries}, " + + $"products +{createdProducts}/~{updatedProducts}"); + return createdSeries + updatedSeries; + } +} diff --git a/SVSim.Bootstrap/Models/Seed/SleeveShopSeed.cs b/SVSim.Bootstrap/Models/Seed/SleeveShopSeed.cs new file mode 100644 index 0000000..98b1428 --- /dev/null +++ b/SVSim.Bootstrap/Models/Seed/SleeveShopSeed.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace SVSim.Bootstrap.Models.Seed; + +public sealed class SleeveShopSeriesSeed +{ + [JsonPropertyName("series_id")] public int SeriesId { get; set; } + [JsonPropertyName("is_new")] public bool IsNew { get; set; } + [JsonPropertyName("products")] public List Products { get; set; } = new(); +} + +public sealed class SleeveShopProductSeed +{ + [JsonPropertyName("product_id")] public int ProductId { get; set; } + [JsonPropertyName("name_key")] public string NameKey { get; set; } = ""; + [JsonPropertyName("price_crystal")] public int? PriceCrystal { get; set; } + [JsonPropertyName("price_rupy")] public int? PriceRupy { get; set; } + [JsonPropertyName("rewards")] public List Rewards { get; set; } = new(); +} + +public sealed class SleeveShopRewardSeed +{ + [JsonPropertyName("order_index")] public int OrderIndex { get; set; } + [JsonPropertyName("reward_type")] public int RewardType { get; set; } + [JsonPropertyName("reward_detail_id")] public long RewardDetailId { get; set; } + [JsonPropertyName("reward_number")] public int RewardNumber { get; set; } +} diff --git a/SVSim.Bootstrap/Program.cs b/SVSim.Bootstrap/Program.cs index d556abd..73eed05 100644 --- a/SVSim.Bootstrap/Program.cs +++ b/SVSim.Bootstrap/Program.cs @@ -98,6 +98,7 @@ public static class Program await new PracticeOpponentImporter().ImportAsync(context, opts.SeedDir); await new PaymentItemImporter().ImportAsync(context, opts.SeedDir); await new ItemImporter().ImportAsync(context, opts.SeedDir); + await new SleeveShopImporter().ImportAsync(context, opts.SeedDir); var puzzleImporter = new PuzzleImporter(); await puzzleImporter.ImportGroupsAsync(context, opts.SeedDir); await puzzleImporter.ImportPuzzlesAsync(context, opts.SeedDir); diff --git a/SVSim.Database/Migrations/20260528015716_AddSleeveShop.Designer.cs b/SVSim.Database/Migrations/20260528015716_AddSleeveShop.Designer.cs new file mode 100644 index 0000000..ba4ac2f --- /dev/null +++ b/SVSim.Database/Migrations/20260528015716_AddSleeveShop.Designer.cs @@ -0,0 +1,3383 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SVSim.Database; + +#nullable disable + +namespace SVSim.Database.Migrations +{ + [DbContext(typeof(SVSimDbContext))] + [Migration("20260528015716_AddSleeveShop")] + partial class AddSleeveShop + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.HasSequence("ShortUdidSequence") + .StartsAt(400000000L); + + modelBuilder.Entity("DegreeEntryViewer", b => + { + b.Property("DegreesId") + .HasColumnType("integer"); + + b.Property("ViewersId") + .HasColumnType("bigint"); + + b.HasKey("DegreesId", "ViewersId"); + + b.HasIndex("ViewersId"); + + b.ToTable("DegreeEntryViewer"); + }); + + modelBuilder.Entity("EmblemEntryViewer", b => + { + b.Property("EmblemsId") + .HasColumnType("integer"); + + b.Property("ViewersId") + .HasColumnType("bigint"); + + b.HasKey("EmblemsId", "ViewersId"); + + b.HasIndex("ViewersId"); + + b.ToTable("EmblemEntryViewer"); + }); + + modelBuilder.Entity("LeaderSkinEntryViewer", b => + { + b.Property("LeaderSkinsId") + .HasColumnType("integer"); + + b.Property("ViewersId") + .HasColumnType("bigint"); + + b.HasKey("LeaderSkinsId", "ViewersId"); + + b.HasIndex("ViewersId"); + + b.ToTable("LeaderSkinEntryViewer"); + }); + + modelBuilder.Entity("MyPageBackgroundEntryViewer", b => + { + b.Property("MyPageBackgroundsId") + .HasColumnType("integer"); + + b.Property("ViewersId") + .HasColumnType("bigint"); + + b.HasKey("MyPageBackgroundsId", "ViewersId"); + + b.HasIndex("ViewersId"); + + b.ToTable("MyPageBackgroundEntryViewer"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.SpecialBattleSetting", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("BanishEffectOverride") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClassDestroyEffectOverride") + .HasColumnType("integer"); + + b.Property("EnemyAttachSkill") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnemyStartLife") + .HasColumnType("integer"); + + b.Property("EnemyStartPp") + .HasColumnType("integer"); + + b.Property("IdOverrideInBattleLog") + .IsRequired() + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("PlayerAttachSkill") + .IsRequired() + .HasColumnType("text"); + + b.Property("PlayerFirstTurn") + .HasColumnType("integer"); + + b.Property("PlayerStartLife") + .HasColumnType("integer"); + + b.Property("PlayerStartPp") + .HasColumnType("integer"); + + b.Property("ResultSkip") + .HasColumnType("integer"); + + b.Property("SpecialTokenDrawEffectOverride") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenDrawEffectOverride") + .IsRequired() + .HasColumnType("text"); + + b.Property("VsEffectOverride") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("SpecialBattleSettings"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.StoryChapter", b => + { + b.Property("StoryId") + .HasColumnType("integer"); + + b.Property("Battle3dFieldId") + .HasColumnType("integer"); + + b.Property("BattleExists") + .HasColumnType("boolean"); + + b.Property("BgFileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("BgmId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChapterClearTextId") + .HasColumnType("text"); + + b.Property("ChapterEffectPath") + .HasColumnType("text"); + + b.Property("ChapterId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer"); + + b.Property("EnemyAiId") + .HasColumnType("integer"); + + b.Property("EnemyCharaId") + .HasColumnType("integer"); + + b.Property("EnemyClass") + .HasColumnType("integer"); + + b.Property("IsCameraMovable") + .HasColumnType("integer"); + + b.Property("IsMaintenanceChapter") + .HasColumnType("boolean"); + + b.Property("IsPlayAnotherEndAppearanceAnimation") + .HasColumnType("boolean"); + + b.Property("IsReleasedAnotherEnd") + .HasColumnType("boolean"); + + b.Property("IsSkipEnabled") + .HasColumnType("boolean"); + + b.Property("NextChapterId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReleasePoint") + .HasColumnType("integer"); + + b.Property("RequiredChapterId") + .HasColumnType("text"); + + b.Property("SectionId") + .HasColumnType("integer"); + + b.Property("SelectionDisplayPosition") + .HasColumnType("text"); + + b.Property("SelectionTextId") + .HasColumnType("text"); + + b.Property("ShowCoordinate") + .HasColumnType("integer"); + + b.Property("ShowSubtitles") + .HasColumnType("integer"); + + b.Property("SpecialBattleSettingId") + .HasColumnType("integer"); + + b.Property("UnlockText") + .HasColumnType("text"); + + b.Property("XCoordinate") + .HasColumnType("numeric"); + + b.Property("YCoordinate") + .HasColumnType("numeric"); + + b.HasKey("StoryId"); + + b.HasIndex("NextChapterId"); + + b.HasIndex("SpecialBattleSettingId"); + + b.HasIndex("SectionId", "CharaId", "ChapterId"); + + b.ToTable("StoryChapters"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.StorySection", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AllStoryOrderId") + .HasColumnType("integer"); + + b.Property("BackGroundId") + .HasColumnType("integer"); + + b.Property("ChapterSelectType") + .HasColumnType("integer"); + + b.Property("ImageName") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsLeaderSelect") + .HasColumnType("boolean"); + + b.Property("IsPlayAnotherEndAppearanceAnimation") + .HasColumnType("boolean"); + + b.Property("IsSpoiler") + .HasColumnType("integer"); + + b.Property("IsUnderMaintenance") + .HasColumnType("boolean"); + + b.Property("NameTextKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("SpoilerMessage") + .IsRequired() + .HasColumnType("text"); + + b.Property("StoryApiType") + .HasColumnType("integer"); + + b.Property("StoryTypeOverwrite") + .HasColumnType("integer"); + + b.Property("WorldId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("WorldId"); + + b.ToTable("StorySections"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.StoryWorld", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("PanelImageName") + .IsRequired() + .HasColumnType("text"); + + b.Property("RibbonText") + .IsRequired() + .HasColumnType("text"); + + b.Property("TitleTextKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("StoryWorlds"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.ViewerStoryBranchUnlock", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("StoryId") + .HasColumnType("integer"); + + b.Property("UnlockedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ViewerId", "StoryId"); + + b.ToTable("ViewerStoryBranchUnlocks"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.ViewerStoryProgress", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("StoryId") + .HasColumnType("integer"); + + b.Property("FinishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsFinish") + .HasColumnType("boolean"); + + b.Property("IsSkipped") + .HasColumnType("boolean"); + + b.Property("SkippedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ViewerId", "StoryId"); + + b.ToTable("ViewerStoryProgress"); + }); + + modelBuilder.Entity("SVSim.Database.Models.AchievementCatalogEntry", b => + { + b.Property("AchievementType") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("EventArg") + .HasColumnType("integer"); + + b.Property("EventType") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderNum") + .HasColumnType("integer"); + + b.Property("RequireNumber") + .HasColumnType("integer"); + + b.Property("RewardDetailId") + .HasColumnType("bigint"); + + b.Property("RewardNumber") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.HasKey("AchievementType", "Level"); + + b.HasIndex("AchievementType"); + + b.HasIndex("EventType", "EventArg"); + + b.ToTable("AchievementCatalog"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ArenaSeasonConfig", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Cost") + .HasColumnType("numeric(20,0)"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Enable") + .HasColumnType("integer"); + + b.Property("FormatInfo") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("IsJoin") + .HasColumnType("boolean"); + + b.Property("Mode") + .HasColumnType("integer"); + + b.Property("RupyCost") + .HasColumnType("numeric(20,0)"); + + b.Property("TicketCost") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ArenaSeasons"); + }); + + modelBuilder.Entity("SVSim.Database.Models.AvatarAbilityEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Ability") + .IsRequired() + .HasColumnType("text"); + + b.Property("AbilityCost") + .IsRequired() + .HasColumnType("text"); + + b.Property("AbilityDesc") + .IsRequired() + .HasColumnType("text"); + + b.Property("BattleStartFirstPlayerTurnBp") + .HasColumnType("integer"); + + b.Property("BattleStartMaxLife") + .HasColumnType("integer"); + + b.Property("BattleStartSecondPlayerTurnBp") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaderSkinId") + .HasColumnType("integer"); + + b.Property("PassiveAbility") + .IsRequired() + .HasColumnType("text"); + + b.Property("PassiveAbilityDesc") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AvatarAbilities"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BannerEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("ChangeTime") + .HasColumnType("integer"); + + b.Property("Click") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("ImageName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ImagePaths") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RemainingTime") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Banners"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlePassLevelEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("RequiredPoint") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("BattlePassLevels"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlePassMonthlyMissionEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BattlePassPoint") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("EventArg") + .HasColumnType("integer"); + + b.Property("EventType") + .HasColumnType("text"); + + b.Property("Month") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderNum") + .HasColumnType("integer"); + + b.Property("RequireNumber") + .HasColumnType("integer"); + + b.Property("RewardDetailId") + .HasColumnType("bigint"); + + b.Property("RewardNumber") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Year", "Month"); + + b.HasIndex("Year", "Month", "OrderNum") + .IsUnique(); + + b.ToTable("BattlePassMonthlyMissions"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlePassRewardEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAppealExclusion") + .HasColumnType("boolean"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("RewardDetailId") + .HasColumnType("bigint"); + + b.Property("RewardNumber") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.Property("SeasonId") + .HasColumnType("integer"); + + b.Property("Track") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId", "Track", "Level") + .IsUnique(); + + b.ToTable("BattlePassRewards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlePassSeasonEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CanPurchase") + .HasColumnType("boolean"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("MaxLevel") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriceCrystal") + .HasColumnType("integer"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StartDate", "EndDate"); + + b.ToTable("BattlePassSeasons"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlefieldEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsOpen") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Battlefields"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BuildDeckProductEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DeckCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("FeaturedCardId") + .HasColumnType("bigint"); + + b.Property("IntroPriceCrystal") + .HasColumnType("integer"); + + b.Property("IntroPriceRupy") + .HasColumnType("integer"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LeaderId") + .HasColumnType("integer"); + + b.Property("ProductNameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("PurchaseNumMax") + .HasColumnType("integer"); + + b.Property("RegularPriceCrystal") + .HasColumnType("integer"); + + b.Property("RegularPriceRupy") + .HasColumnType("integer"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("BuildDeckProducts"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BuildDeckSeriesEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DrumrollPath") + .IsRequired() + .HasColumnType("text"); + + b.Property("IntroKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.Property("NameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderIndex") + .HasColumnType("integer"); + + b.Property("TitlePath") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("BuildDeckSeries"); + }); + + modelBuilder.Entity("SVSim.Database.Models.CardCosmeticReward", b => + { + b.Property("CardId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("CosmeticId") + .HasColumnType("bigint"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("CardId", "Type", "CosmeticId"); + + b.HasIndex("CardId"); + + b.ToTable("CardCosmeticRewards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ClassEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Classes"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ClassExpEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("NecessaryExp") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ClassExpCurve"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ColosseumConfig", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CardPoolName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ColosseumId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ColosseumName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DeckFormat") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAllCardEnabled") + .HasColumnType("integer"); + + b.Property("IsColosseumPeriod") + .HasColumnType("boolean"); + + b.Property("IsDisplayTips") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsNormalTwoPick") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsRoundPeriod") + .HasColumnType("boolean"); + + b.Property("IsSpecialMode") + .IsRequired() + .HasColumnType("text"); + + b.Property("NowRound") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalesPeriodInfo") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("TipsId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Colosseums"); + }); + + modelBuilder.Entity("SVSim.Database.Models.DailyLoginBonusEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("BonusData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("BonusId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("DailyLoginBonuses"); + }); + + modelBuilder.Entity("SVSim.Database.Models.DefaultDeckEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CardIdArray") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ClassId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DeckName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeckNo") + .HasColumnType("integer"); + + b.Property("LeaderSkinId") + .HasColumnType("integer"); + + b.Property("SleeveId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("DefaultDecks"); + }); + + modelBuilder.Entity("SVSim.Database.Models.DegreeEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Degrees"); + }); + + modelBuilder.Entity("SVSim.Database.Models.EmblemEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Emblems"); + }); + + modelBuilder.Entity("SVSim.Database.Models.FeatureMaintenanceEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("FeatureKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FeatureMaintenances"); + }); + + modelBuilder.Entity("SVSim.Database.Models.GameConfigSection", b => + { + b.Property("SectionName") + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("ValueJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("SectionName"); + + b.ToTable("GameConfigs"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ItemEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("ClassId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("EmoteId") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClassId"); + + b.ToTable("LeaderSkins"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LoadingExclusionCardEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("CardId") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("LoadingExclusionCards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.MaintenanceCardEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("CardId") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("MaintenanceCards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.MasterPointRankingPeriodEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("BeginTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("NecessaryScore") + .HasColumnType("bigint"); + + b.Property("PeriodNum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("MasterPointRankingPeriods"); + }); + + modelBuilder.Entity("SVSim.Database.Models.MissionCatalogEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("BattlePassPoint") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultFlag") + .HasColumnType("boolean"); + + b.Property("EndTime") + .HasColumnType("bigint"); + + b.Property("EventArg") + .HasColumnType("integer"); + + b.Property("EventType") + .HasColumnType("text"); + + b.Property("LotType") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequireNumber") + .HasColumnType("integer"); + + b.Property("RewardDetailId") + .HasColumnType("bigint"); + + b.Property("RewardNumber") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("LotType"); + + b.HasIndex("EventType", "EventArg"); + + b.ToTable("MissionCatalog"); + }); + + modelBuilder.Entity("SVSim.Database.Models.MyPageBackgroundEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("MyPageBackgrounds"); + }); + + modelBuilder.Entity("SVSim.Database.Models.MyRotationAbilityEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AbilityId") + .HasColumnType("integer"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("MyRotationAbilities"); + }); + + modelBuilder.Entity("SVSim.Database.Models.MyRotationSettingEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AbilitiesCsv") + .IsRequired() + .HasColumnType("text"); + + b.Property("CardSetIdsCsv") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("ReprintedCardIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RestrictedCardIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RotationId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("MyRotationSettings"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PackConfigEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("BasePackId") + .HasColumnType("integer"); + + b.Property("CommenceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CompleteDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("GachaDetail") + .IsRequired() + .HasColumnType("text"); + + b.Property("GachaType") + .HasColumnType("integer"); + + b.Property("IsHide") + .HasColumnType("boolean"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.Property("IsPreRelease") + .HasColumnType("boolean"); + + b.Property("OpenCountLimit") + .HasColumnType("integer"); + + b.Property("OverrideDrawEffectPackId") + .HasColumnType("integer"); + + b.Property("OverrideUiEffectPackId") + .HasColumnType("integer"); + + b.Property("PackCategory") + .HasColumnType("integer"); + + b.Property("PosterType") + .HasColumnType("integer"); + + b.Property("SalesPeriodTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SleeveId") + .HasColumnType("integer"); + + b.Property("SpecialSleeveId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Packs"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PaymentItemEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("ChargeCrystalNum") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("FreeCrystalNum") + .HasColumnType("integer"); + + b.Property("ImageName") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsResaleProduct") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("ProductId") + .HasColumnType("integer"); + + b.Property("PurchaseLimit") + .HasColumnType("integer"); + + b.Property("RemainingTime") + .HasColumnType("integer"); + + b.Property("ResaleStartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SpecialShopFlag") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StoreProductId") + .HasColumnType("bigint"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PaymentItems"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PracticeOpponentEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AiDeckLevel") + .HasColumnType("integer"); + + b.Property("AiLogicLevel") + .HasColumnType("integer"); + + b.Property("AiMaxLife") + .HasColumnType("integer"); + + b.Property("Battle3dFieldId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer"); + + b.Property("ClassId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DegreeId") + .HasColumnType("integer"); + + b.Property("IsCampaignPractice") + .HasColumnType("boolean"); + + b.Property("IsMaintenance") + .HasColumnType("boolean"); + + b.Property("PracticeId") + .HasColumnType("integer"); + + b.Property("TextId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PracticeOpponents"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PreReleaseInfo", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CardMasterId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultCardMasterId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisplayEndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("FreeMatchStartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPreRotationFreeMatchTerm") + .HasColumnType("boolean"); + + b.Property("LatestReprintedBaseCardIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("NextCardSetId") + .IsRequired() + .HasColumnType("text"); + + b.Property("PreReleaseCardMasterId") + .IsRequired() + .HasColumnType("text"); + + b.Property("PreReleaseId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReprintedBaseCardIds") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RotationCardSetIdList") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("PreReleaseInfos"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PuzzleEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("IsAdditional") + .HasColumnType("boolean"); + + b.Property("IsPlayable") + .HasColumnType("boolean"); + + b.Property("PuzzleDifficulty") + .HasColumnType("integer"); + + b.Property("PuzzleId") + .HasColumnType("integer"); + + b.Property("ReleaseConditionTextId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("Puzzles"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PuzzleGroupEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("BasicTitleTextId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CharaId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DifficultyNameListJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("PuzzleCharaId") + .HasColumnType("integer"); + + b.Property("PuzzleMasterId") + .HasColumnType("integer"); + + b.Property("SortType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("PuzzleGroups"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PuzzleMissionEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AchievedMessage") + .IsRequired() + .HasColumnType("text"); + + b.Property("CampaignCommenceTime") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("MissionName") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrderId") + .HasColumnType("integer"); + + b.Property("RequireNumber") + .HasColumnType("integer"); + + b.Property("RewardDetailId") + .HasColumnType("bigint"); + + b.Property("RewardNumber") + .HasColumnType("integer"); + + b.Property("RewardType") + .HasColumnType("integer"); + + b.Property("TargetPuzzleGroupId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("PuzzleMissions"); + }); + + modelBuilder.Entity("SVSim.Database.Models.RankInfoEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AccumulateMasterPoint") + .HasColumnType("integer"); + + b.Property("AccumulatePoint") + .HasColumnType("integer"); + + b.Property("BaseAddBp") + .HasColumnType("integer"); + + b.Property("BaseDropBp") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPromotionWar") + .HasColumnType("integer"); + + b.Property("LoseBonus") + .HasColumnType("double precision"); + + b.Property("LowerLimitPoint") + .HasColumnType("integer"); + + b.Property("MatchCount") + .HasColumnType("integer"); + + b.Property("MaxLoseBonus") + .HasColumnType("integer"); + + b.Property("MaxWinBonus") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NecessaryPoint") + .HasColumnType("integer"); + + b.Property("NecessaryWin") + .HasColumnType("integer"); + + b.Property("ResetLose") + .HasColumnType("integer"); + + b.Property("StreakBonusPt") + .HasColumnType("integer"); + + b.Property("WinBonus") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.ToTable("RankInfo"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ReprintedCardEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("CardId") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("ReprintedCards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SealedConfig", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CrystalCost") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DeckUsingNumMin") + .HasColumnType("integer"); + + b.Property("Enable") + .HasColumnType("integer"); + + b.Property("IsDeckCodeMaintenance") + .HasColumnType("boolean"); + + b.Property("IsJoin") + .HasColumnType("boolean"); + + b.Property("PackInfo") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RupyCost") + .HasColumnType("integer"); + + b.Property("SalesPeriodInfo") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ScheduleId") + .HasColumnType("integer"); + + b.Property("TicketCost") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("SealedSeasons"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ShadowverseCardEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("Attack") + .HasColumnType("integer"); + + b.Property("ClassId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Defense") + .HasColumnType("integer"); + + b.Property("IsFoil") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PrimaryResourceCost") + .HasColumnType("integer"); + + b.Property("Rarity") + .HasColumnType("integer"); + + b.Property("ShadowverseCardSetEntryId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClassId"); + + b.HasIndex("ShadowverseCardSetEntryId"); + + b.ToTable("Cards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ShadowverseCardSetEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBasic") + .HasColumnType("boolean"); + + b.Property("IsInRotation") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("CardSets"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ShadowverseDeckEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClassId") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Format") + .HasColumnType("integer"); + + b.Property("LeaderSkinId") + .HasColumnType("integer"); + + b.Property("MyRotationId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("RandomLeaderSkin") + .HasColumnType("boolean"); + + b.Property("SleeveId") + .HasColumnType("integer"); + + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClassId"); + + b.HasIndex("LeaderSkinId"); + + b.HasIndex("SleeveId"); + + b.HasIndex("ViewerId"); + + b.ToTable("Decks"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SleeveEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Sleeves"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SleeveShopProductEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("NameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriceCrystal") + .HasColumnType("integer"); + + b.Property("PriceRupy") + .HasColumnType("integer"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("SleeveShopProducts"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SleeveShopSeriesEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("SleeveShopSeries"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SpecialDeckFormatEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DeckFormat") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("SpecialDeckFormats"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SpotCardEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("CardId") + .HasColumnType("bigint"); + + b.Property("Cost") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("SpotCards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.UnlimitedRestrictionEntry", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("CardId") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("RestrictionValue") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("UnlimitedRestrictions"); + }); + + modelBuilder.Entity("SVSim.Database.Models.Viewer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastLogin") + .HasColumnType("timestamp with time zone"); + + b.Property("ShortUdid") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValueSql("nextval('\"ShortUdidSequence\"')"); + + NpgsqlPropertyBuilderExtensions.UseSequence(b.Property("ShortUdid"), "ShortUdidSequence"); + + b.Property("Udid") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ShortUdid"); + + b.HasIndex("Udid") + .IsUnique(); + + b.ToTable("Viewers"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerAchievement", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("AchievementType") + .HasColumnType("integer"); + + b.Property("AchievementStatus") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("NowAchievedLevel") + .HasColumnType("integer"); + + b.Property("ResultAnnounceSawLevel") + .HasColumnType("integer"); + + b.HasKey("ViewerId", "AchievementType"); + + b.ToTable("ViewerAchievements"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerBattlePassClaimEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("SeasonId") + .HasColumnType("integer"); + + b.Property("Track") + .HasColumnType("integer"); + + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ViewerId", "SeasonId"); + + b.HasIndex("ViewerId", "SeasonId", "Track", "Level") + .IsUnique(); + + b.ToTable("ViewerBattlePassClaims"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerBattlePassProgressEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentPoint") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPremium") + .HasColumnType("boolean"); + + b.Property("SeasonId") + .HasColumnType("integer"); + + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("WeeklyPeriodStart") + .HasColumnType("timestamp with time zone"); + + b.Property("WeeklyPoints") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ViewerId", "SeasonId") + .IsUnique(); + + b.ToTable("ViewerBattlePassProgress"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerEventCounter", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("EventKey") + .HasColumnType("text"); + + b.Property("Period") + .HasColumnType("text"); + + b.Property("Count") + .HasColumnType("integer"); + + b.HasKey("ViewerId", "EventKey", "Period"); + + b.HasIndex("ViewerId", "Period"); + + b.ToTable("ViewerEventCounters"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerMission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssignedAt") + .HasColumnType("bigint"); + + b.Property("ClaimedAt") + .HasColumnType("bigint"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("MissionCatalogId") + .HasColumnType("integer"); + + b.Property("MissionStatus") + .HasColumnType("integer"); + + b.Property("Slot") + .HasColumnType("integer"); + + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ViewerId"); + + b.HasIndex("ViewerId", "Slot") + .IsUnique(); + + b.ToTable("ViewerMissions"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerPuzzleClear", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("PuzzleId") + .HasColumnType("integer"); + + b.Property("BestRetryCount") + .HasColumnType("integer"); + + b.Property("ClearedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ViewerId", "PuzzleId"); + + b.ToTable("ViewerPuzzleClears"); + }); + + modelBuilder.Entity("SleeveEntryViewer", b => + { + b.Property("SleevesId") + .HasColumnType("integer"); + + b.Property("ViewersId") + .HasColumnType("bigint"); + + b.HasKey("SleevesId", "ViewersId"); + + b.HasIndex("ViewersId"); + + b.ToTable("SleeveEntryViewer"); + }); + + modelBuilder.Entity("DegreeEntryViewer", b => + { + b.HasOne("SVSim.Database.Models.DegreeEntry", null) + .WithMany() + .HasForeignKey("DegreesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany() + .HasForeignKey("ViewersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmblemEntryViewer", b => + { + b.HasOne("SVSim.Database.Models.EmblemEntry", null) + .WithMany() + .HasForeignKey("EmblemsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany() + .HasForeignKey("ViewersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LeaderSkinEntryViewer", b => + { + b.HasOne("SVSim.Database.Models.LeaderSkinEntry", null) + .WithMany() + .HasForeignKey("LeaderSkinsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany() + .HasForeignKey("ViewersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("MyPageBackgroundEntryViewer", b => + { + b.HasOne("SVSim.Database.Models.MyPageBackgroundEntry", null) + .WithMany() + .HasForeignKey("MyPageBackgroundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany() + .HasForeignKey("ViewersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.StoryChapter", b => + { + b.HasOne("SVSim.Database.Entities.Story.StorySection", "Section") + .WithMany() + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Entities.Story.SpecialBattleSetting", "SpecialBattleSetting") + .WithMany() + .HasForeignKey("SpecialBattleSettingId"); + + b.OwnsMany("SVSim.Database.Entities.Story.StoryChapterBattleSetting", "BattleSettings", b1 => + { + b1.Property("StoryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("Battle3dFieldIdOverride") + .HasColumnType("integer"); + + b1.Property("BgmIdOverride") + .HasColumnType("integer"); + + b1.Property("DeckClassId") + .HasColumnType("integer"); + + b1.Property("DeckSkinIdOverride") + .HasColumnType("integer"); + + b1.Property("EnemyEmotionOverride") + .HasColumnType("integer"); + + b1.Property("PlayerEmotionOverride") + .HasColumnType("integer"); + + b1.Property("SkinIdOverride") + .HasColumnType("integer"); + + b1.HasKey("StoryId", "Id"); + + b1.ToTable("StoryChapterBattleSetting"); + + b1.WithOwner() + .HasForeignKey("StoryId"); + }); + + b.OwnsMany("SVSim.Database.Entities.Story.StoryChapterReward", "Rewards", b1 => + { + b1.Property("StoryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("RewardDetailId") + .HasColumnType("bigint"); + + b1.Property("RewardNumber") + .HasColumnType("integer"); + + b1.Property("RewardType") + .HasColumnType("integer"); + + b1.HasKey("StoryId", "Id"); + + b1.ToTable("StoryChapterReward"); + + b1.WithOwner() + .HasForeignKey("StoryId"); + }); + + b.OwnsMany("SVSim.Database.Entities.Story.StorySubChapter", "SubChapters", b1 => + { + b1.Property("StoryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("IsMaintenanceChapter") + .HasColumnType("boolean"); + + b1.Property("SubChapterId") + .HasColumnType("integer"); + + b1.Property("SubChapterStoryId") + .HasColumnType("integer"); + + b1.HasKey("StoryId", "Id"); + + b1.ToTable("StorySubChapter"); + + b1.WithOwner() + .HasForeignKey("StoryId"); + }); + + b.Navigation("BattleSettings"); + + b.Navigation("Rewards"); + + b.Navigation("Section"); + + b.Navigation("SpecialBattleSetting"); + + b.Navigation("SubChapters"); + }); + + modelBuilder.Entity("SVSim.Database.Entities.Story.StorySection", b => + { + b.HasOne("SVSim.Database.Entities.Story.StoryWorld", "World") + .WithMany() + .HasForeignKey("WorldId"); + + b.Navigation("World"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlePassRewardEntry", b => + { + b.HasOne("SVSim.Database.Models.BattlePassSeasonEntry", "Season") + .WithMany("Rewards") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BuildDeckProductEntry", b => + { + b.HasOne("SVSim.Database.Models.BuildDeckSeriesEntry", "Series") + .WithMany("Products") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("SVSim.Database.Models.BuildDeckProductCardEntry", "Cards", b1 => + { + b1.Property("BuildDeckProductEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("CardId") + .HasColumnType("bigint"); + + b1.Property("IsSpot") + .HasColumnType("boolean"); + + b1.Property("Number") + .HasColumnType("integer"); + + b1.HasKey("BuildDeckProductEntryId", "Id"); + + b1.ToTable("BuildDeckProductCardEntry"); + + b1.WithOwner() + .HasForeignKey("BuildDeckProductEntryId"); + }); + + b.OwnsMany("SVSim.Database.Models.BuildDeckProductRewardEntry", "Rewards", b1 => + { + b1.Property("BuildDeckProductEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("MessageId") + .HasColumnType("integer"); + + b1.Property("RewardDetailId") + .HasColumnType("bigint"); + + b1.Property("RewardIndex") + .HasColumnType("integer"); + + b1.Property("RewardNumber") + .HasColumnType("integer"); + + b1.Property("RewardType") + .HasColumnType("integer"); + + b1.HasKey("BuildDeckProductEntryId", "Id"); + + b1.ToTable("BuildDeckProductRewardEntry"); + + b1.WithOwner() + .HasForeignKey("BuildDeckProductEntryId"); + }); + + b.Navigation("Cards"); + + b.Navigation("Rewards"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BuildDeckSeriesEntry", b => + { + b.OwnsMany("SVSim.Database.Models.BuildDeckSeriesRewardEntry", "SeriesRewards", b1 => + { + b1.Property("BuildDeckSeriesEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("ItemIndex") + .HasColumnType("integer"); + + b1.Property("MessageId") + .HasColumnType("integer"); + + b1.Property("RewardDetailId") + .HasColumnType("bigint"); + + b1.Property("RewardNumber") + .HasColumnType("integer"); + + b1.Property("RewardType") + .HasColumnType("integer"); + + b1.Property("TierIndex") + .HasColumnType("integer"); + + b1.HasKey("BuildDeckSeriesEntryId", "Id"); + + b1.ToTable("BuildDeckSeriesRewardEntry"); + + b1.WithOwner() + .HasForeignKey("BuildDeckSeriesEntryId"); + }); + + b.Navigation("SeriesRewards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.CardCosmeticReward", b => + { + b.HasOne("SVSim.Database.Models.ShadowverseCardEntry", "Card") + .WithMany() + .HasForeignKey("CardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Card"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinEntry", b => + { + b.HasOne("SVSim.Database.Models.ClassEntry", "Class") + .WithMany("LeaderSkins") + .HasForeignKey("ClassId"); + + b.Navigation("Class"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PackConfigEntry", b => + { + b.OwnsMany("SVSim.Database.Models.PackBannerEntry", "Banners", b1 => + { + b1.Property("PackConfigEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("BannerName") + .IsRequired() + .HasColumnType("text"); + + b1.Property("DialogTitle") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("PackConfigEntryId", "Id"); + + b1.ToTable("PackBannerEntry"); + + b1.WithOwner() + .HasForeignKey("PackConfigEntryId"); + }); + + b.OwnsMany("SVSim.Database.Models.PackChildGachaEntry", "ChildGachas", b1 => + { + b1.Property("PackConfigEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("CampaignName") + .HasColumnType("text"); + + b1.Property("CardCount") + .HasColumnType("integer"); + + b1.Property("Cost") + .HasColumnType("integer"); + + b1.Property("FreeGachaCampaignId") + .HasColumnType("integer"); + + b1.Property("GachaId") + .HasColumnType("integer"); + + b1.Property("IsDailySingle") + .HasColumnType("boolean"); + + b1.Property("ItemId") + .HasColumnType("bigint"); + + b1.Property("OverrideIncreaseGachaPoint") + .HasColumnType("integer"); + + b1.Property("PurchaseLimitCount") + .HasColumnType("integer"); + + b1.Property("TypeDetail") + .HasColumnType("integer"); + + b1.HasKey("PackConfigEntryId", "Id"); + + b1.ToTable("PackChildGachaEntry"); + + b1.WithOwner() + .HasForeignKey("PackConfigEntryId"); + }); + + b.OwnsOne("SVSim.Database.Models.PackGachaPointConfig", "GachaPointConfig", b1 => + { + b1.Property("PackConfigEntryId") + .HasColumnType("integer"); + + b1.Property("ExchangeablePoint") + .HasColumnType("integer"); + + b1.Property("IncreaseGachaPoint") + .HasColumnType("integer"); + + b1.HasKey("PackConfigEntryId"); + + b1.ToTable("Packs"); + + b1.WithOwner() + .HasForeignKey("PackConfigEntryId"); + }); + + b.Navigation("Banners"); + + b.Navigation("ChildGachas"); + + b.Navigation("GachaPointConfig"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PuzzleEntry", b => + { + b.HasOne("SVSim.Database.Models.PuzzleGroupEntry", "Group") + .WithMany("Puzzles") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ShadowverseCardEntry", b => + { + b.HasOne("SVSim.Database.Models.ClassEntry", "Class") + .WithMany() + .HasForeignKey("ClassId"); + + b.HasOne("SVSim.Database.Models.ShadowverseCardSetEntry", null) + .WithMany("Cards") + .HasForeignKey("ShadowverseCardSetEntryId"); + + b.OwnsOne("SVSim.Database.Models.CardCollectionInfo", "CollectionInfo", b1 => + { + b1.Property("ShadowverseCardEntryId") + .HasColumnType("bigint"); + + b1.Property("CraftCost") + .HasColumnType("integer"); + + b1.Property("DustReward") + .HasColumnType("integer"); + + b1.HasKey("ShadowverseCardEntryId"); + + b1.ToTable("Cards"); + + b1.WithOwner() + .HasForeignKey("ShadowverseCardEntryId"); + }); + + b.Navigation("Class"); + + b.Navigation("CollectionInfo"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ShadowverseDeckEntry", b => + { + b.HasOne("SVSim.Database.Models.ClassEntry", "Class") + .WithMany() + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.LeaderSkinEntry", "LeaderSkin") + .WithMany() + .HasForeignKey("LeaderSkinId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.SleeveEntry", "Sleeve") + .WithMany() + .HasForeignKey("SleeveId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany("Decks") + .HasForeignKey("ViewerId"); + + b.OwnsMany("SVSim.Database.Models.DeckCard", "Cards", b1 => + { + b1.Property("ShadowverseDeckEntryId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("CardId") + .HasColumnType("bigint"); + + b1.Property("Count") + .HasColumnType("integer"); + + b1.HasKey("ShadowverseDeckEntryId", "Id"); + + b1.HasIndex("CardId"); + + b1.ToTable("DeckCard"); + + b1.HasOne("SVSim.Database.Models.ShadowverseCardEntry", "Card") + .WithMany() + .HasForeignKey("CardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("ShadowverseDeckEntryId"); + + b1.Navigation("Card"); + }); + + b.Navigation("Cards"); + + b.Navigation("Class"); + + b.Navigation("LeaderSkin"); + + b.Navigation("Sleeve"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SleeveShopProductEntry", b => + { + b.HasOne("SVSim.Database.Models.SleeveShopSeriesEntry", "Series") + .WithMany("Products") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("SVSim.Database.Models.SleeveShopProductRewardEntry", "Rewards", b1 => + { + b1.Property("SleeveShopProductEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("OrderIndex") + .HasColumnType("integer"); + + b1.Property("RewardDetailId") + .HasColumnType("bigint"); + + b1.Property("RewardNumber") + .HasColumnType("integer"); + + b1.Property("RewardType") + .HasColumnType("integer"); + + b1.HasKey("SleeveShopProductEntryId", "Id"); + + b1.ToTable("SleeveShopProductRewardEntry"); + + b1.WithOwner() + .HasForeignKey("SleeveShopProductEntryId"); + }); + + b.Navigation("Rewards"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("SVSim.Database.Models.Viewer", b => + { + b.OwnsMany("SVSim.Database.Models.OwnedCardEntry", "Cards", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("CardId") + .HasColumnType("bigint"); + + b1.Property("Count") + .HasColumnType("integer"); + + b1.Property("IsProtected") + .HasColumnType("boolean"); + + b1.HasKey("ViewerId", "Id"); + + b1.HasIndex("CardId"); + + b1.HasIndex("ViewerId", "CardId") + .IsUnique(); + + b1.ToTable("OwnedCardEntry"); + + b1.HasOne("SVSim.Database.Models.ShadowverseCardEntry", "Card") + .WithMany() + .HasForeignKey("CardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("ViewerId"); + + b1.Navigation("Card"); + }); + + b.OwnsMany("SVSim.Database.Models.OwnedItemEntry", "Items", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("Count") + .HasColumnType("integer"); + + b1.Property("ItemId") + .HasColumnType("integer"); + + b1.HasKey("ViewerId", "Id"); + + b1.HasIndex("ItemId"); + + b1.HasIndex("ViewerId", "ItemId") + .IsUnique(); + + b1.ToTable("OwnedItemEntry"); + + b1.HasOne("SVSim.Database.Models.ItemEntry", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner("Viewer") + .HasForeignKey("ViewerId"); + + b1.Navigation("Item"); + + b1.Navigation("Viewer"); + }); + + b.OwnsMany("SVSim.Database.Models.SocialAccountConnection", "SocialAccountConnections", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("AccountId") + .HasColumnType("numeric(20,0)"); + + b1.Property("AccountType") + .HasColumnType("integer"); + + b1.HasKey("ViewerId", "Id"); + + b1.HasIndex("AccountType", "AccountId") + .IsUnique(); + + b1.ToTable("SocialAccountConnection"); + + b1.WithOwner("Viewer") + .HasForeignKey("ViewerId"); + + b1.Navigation("Viewer"); + }); + + b.OwnsMany("SVSim.Database.Models.ViewerBuildDeckProductPurchase", "BuildDeckPurchases", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("ProductId") + .HasColumnType("integer"); + + b1.Property("PurchaseCount") + .HasColumnType("integer"); + + b1.HasKey("ViewerId", "Id"); + + b1.HasIndex("ViewerId", "ProductId") + .IsUnique(); + + b1.ToTable("ViewerBuildDeckProductPurchase"); + + b1.WithOwner() + .HasForeignKey("ViewerId"); + }); + + b.OwnsMany("SVSim.Database.Models.ViewerClassData", "Classes", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("ClassId") + .HasColumnType("integer"); + + b1.Property("Exp") + .HasColumnType("integer"); + + b1.Property("LeaderSkinId") + .HasColumnType("integer"); + + b1.Property("Level") + .HasColumnType("integer"); + + b1.HasKey("ViewerId", "Id"); + + b1.HasIndex("ClassId"); + + b1.HasIndex("LeaderSkinId"); + + b1.ToTable("ViewerClassData"); + + b1.HasOne("SVSim.Database.Models.ClassEntry", "Class") + .WithMany() + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.HasOne("SVSim.Database.Models.LeaderSkinEntry", "LeaderSkin") + .WithMany() + .HasForeignKey("LeaderSkinId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner("Viewer") + .HasForeignKey("ViewerId"); + + b1.Navigation("Class"); + + b1.Navigation("LeaderSkin"); + + b1.Navigation("Viewer"); + }); + + b.OwnsOne("SVSim.Database.Models.ViewerCurrency", "Currency", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("AndroidCrystals") + .HasColumnType("numeric(20,0)"); + + b1.Property("Crystals") + .HasColumnType("numeric(20,0)"); + + b1.Property("DmmCrystals") + .HasColumnType("numeric(20,0)"); + + b1.Property("FreeCrystals") + .HasColumnType("numeric(20,0)"); + + b1.Property("IosCrystals") + .HasColumnType("numeric(20,0)"); + + b1.Property("LifeTotalCrystals") + .HasColumnType("numeric(20,0)"); + + b1.Property("RedEther") + .HasColumnType("numeric(20,0)"); + + b1.Property("Rupees") + .HasColumnType("numeric(20,0)"); + + b1.Property("SteamCrystals") + .HasColumnType("numeric(20,0)"); + + b1.HasKey("ViewerId"); + + b1.ToTable("Viewers"); + + b1.WithOwner() + .HasForeignKey("ViewerId"); + }); + + b.OwnsOne("SVSim.Database.Models.ViewerInfo", "Info", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b1.Property("CountryCode") + .IsRequired() + .HasColumnType("text"); + + b1.Property("IsOfficial") + .HasColumnType("boolean"); + + b1.Property("IsOfficialMarkDisplayed") + .HasColumnType("boolean"); + + b1.Property("MaxFriends") + .HasColumnType("integer"); + + b1.Property("SelectedDegreeId") + .HasColumnType("integer"); + + b1.Property("SelectedEmblemId") + .HasColumnType("integer"); + + b1.HasKey("ViewerId"); + + b1.HasIndex("SelectedDegreeId"); + + b1.HasIndex("SelectedEmblemId"); + + b1.ToTable("Viewers"); + + b1.HasOne("SVSim.Database.Models.DegreeEntry", "SelectedDegree") + .WithMany() + .HasForeignKey("SelectedDegreeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.HasOne("SVSim.Database.Models.EmblemEntry", "SelectedEmblem") + .WithMany() + .HasForeignKey("SelectedEmblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.WithOwner() + .HasForeignKey("ViewerId"); + + b1.Navigation("SelectedDegree"); + + b1.Navigation("SelectedEmblem"); + }); + + b.OwnsOne("SVSim.Database.Models.ViewerMissionData", "MissionData", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("HasReceivedPickTwoMission") + .HasColumnType("boolean"); + + b1.Property("MissionChangeTime") + .HasColumnType("timestamp with time zone"); + + b1.Property("MissionReceiveType") + .HasColumnType("integer"); + + b1.Property("TutorialState") + .HasColumnType("integer"); + + b1.HasKey("ViewerId"); + + b1.ToTable("Viewers"); + + b1.WithOwner() + .HasForeignKey("ViewerId"); + }); + + b.OwnsMany("SVSim.Database.Models.ViewerPackOpenCount", "PackOpenCounts", b1 => + { + b1.Property("ViewerId") + .HasColumnType("bigint"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("LastDailyFreeAt") + .HasColumnType("timestamp with time zone"); + + b1.Property("OpenCount") + .HasColumnType("integer"); + + b1.Property("PackId") + .HasColumnType("integer"); + + b1.HasKey("ViewerId", "Id"); + + b1.ToTable("ViewerPackOpenCount"); + + b1.WithOwner() + .HasForeignKey("ViewerId"); + }); + + b.Navigation("BuildDeckPurchases"); + + b.Navigation("Cards"); + + b.Navigation("Classes"); + + b.Navigation("Currency") + .IsRequired(); + + b.Navigation("Info") + .IsRequired(); + + b.Navigation("Items"); + + b.Navigation("MissionData") + .IsRequired(); + + b.Navigation("PackOpenCounts"); + + b.Navigation("SocialAccountConnections"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerAchievement", b => + { + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany("Achievements") + .HasForeignKey("ViewerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerEventCounter", b => + { + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany("EventCounters") + .HasForeignKey("ViewerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SVSim.Database.Models.ViewerMission", b => + { + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany("Missions") + .HasForeignKey("ViewerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SleeveEntryViewer", b => + { + b.HasOne("SVSim.Database.Models.SleeveEntry", null) + .WithMany() + .HasForeignKey("SleevesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SVSim.Database.Models.Viewer", null) + .WithMany() + .HasForeignKey("ViewersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SVSim.Database.Models.BattlePassSeasonEntry", b => + { + b.Navigation("Rewards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.BuildDeckSeriesEntry", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ClassEntry", b => + { + b.Navigation("LeaderSkins"); + }); + + modelBuilder.Entity("SVSim.Database.Models.PuzzleGroupEntry", b => + { + b.Navigation("Puzzles"); + }); + + modelBuilder.Entity("SVSim.Database.Models.ShadowverseCardSetEntry", b => + { + b.Navigation("Cards"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SleeveShopSeriesEntry", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("SVSim.Database.Models.Viewer", b => + { + b.Navigation("Achievements"); + + b.Navigation("Decks"); + + b.Navigation("EventCounters"); + + b.Navigation("Missions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SVSim.Database/Migrations/20260528015716_AddSleeveShop.cs b/SVSim.Database/Migrations/20260528015716_AddSleeveShop.cs new file mode 100644 index 0000000..3a8ce22 --- /dev/null +++ b/SVSim.Database/Migrations/20260528015716_AddSleeveShop.cs @@ -0,0 +1,96 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace SVSim.Database.Migrations +{ + /// + public partial class AddSleeveShop : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SleeveShopSeries", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + IsNew = table.Column(type: "boolean", nullable: false), + IsEnabled = table.Column(type: "boolean", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: false), + DateUpdated = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SleeveShopSeries", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SleeveShopProducts", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + SeriesId = table.Column(type: "integer", nullable: false), + NameKey = table.Column(type: "text", nullable: false), + PriceCrystal = table.Column(type: "integer", nullable: true), + PriceRupy = table.Column(type: "integer", nullable: true), + IsEnabled = table.Column(type: "boolean", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: false), + DateUpdated = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SleeveShopProducts", x => x.Id); + table.ForeignKey( + name: "FK_SleeveShopProducts_SleeveShopSeries_SeriesId", + column: x => x.SeriesId, + principalTable: "SleeveShopSeries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SleeveShopProductRewardEntry", + columns: table => new + { + SleeveShopProductEntryId = table.Column(type: "integer", nullable: false), + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + OrderIndex = table.Column(type: "integer", nullable: false), + RewardType = table.Column(type: "integer", nullable: false), + RewardDetailId = table.Column(type: "bigint", nullable: false), + RewardNumber = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SleeveShopProductRewardEntry", x => new { x.SleeveShopProductEntryId, x.Id }); + table.ForeignKey( + name: "FK_SleeveShopProductRewardEntry_SleeveShopProducts_SleeveShopP~", + column: x => x.SleeveShopProductEntryId, + principalTable: "SleeveShopProducts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SleeveShopProducts_SeriesId", + table: "SleeveShopProducts", + column: "SeriesId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SleeveShopProductRewardEntry"); + + migrationBuilder.DropTable( + name: "SleeveShopProducts"); + + migrationBuilder.DropTable( + name: "SleeveShopSeries"); + } + } +} diff --git a/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs b/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs index ef10e03..ba91dd9 100644 --- a/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs +++ b/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs @@ -1964,6 +1964,62 @@ namespace SVSim.Database.Migrations b.ToTable("Sleeves"); }); + modelBuilder.Entity("SVSim.Database.Models.SleeveShopProductEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("NameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriceCrystal") + .HasColumnType("integer"); + + b.Property("PriceRupy") + .HasColumnType("integer"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("SleeveShopProducts"); + }); + + modelBuilder.Entity("SVSim.Database.Models.SleeveShopSeriesEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("IsNew") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("SleeveShopSeries"); + }); + modelBuilder.Entity("SVSim.Database.Models.SpecialDeckFormatEntry", b => { b.Property("Id") @@ -2828,6 +2884,50 @@ namespace SVSim.Database.Migrations b.Navigation("Sleeve"); }); + modelBuilder.Entity("SVSim.Database.Models.SleeveShopProductEntry", b => + { + b.HasOne("SVSim.Database.Models.SleeveShopSeriesEntry", "Series") + .WithMany("Products") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("SVSim.Database.Models.SleeveShopProductRewardEntry", "Rewards", b1 => + { + b1.Property("SleeveShopProductEntryId") + .HasColumnType("integer"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("OrderIndex") + .HasColumnType("integer"); + + b1.Property("RewardDetailId") + .HasColumnType("bigint"); + + b1.Property("RewardNumber") + .HasColumnType("integer"); + + b1.Property("RewardType") + .HasColumnType("integer"); + + b1.HasKey("SleeveShopProductEntryId", "Id"); + + b1.ToTable("SleeveShopProductRewardEntry"); + + b1.WithOwner() + .HasForeignKey("SleeveShopProductEntryId"); + }); + + b.Navigation("Rewards"); + + b.Navigation("Series"); + }); + modelBuilder.Entity("SVSim.Database.Models.Viewer", b => { b.OwnsMany("SVSim.Database.Models.OwnedCardEntry", "Cards", b1 => @@ -3259,6 +3359,11 @@ namespace SVSim.Database.Migrations b.Navigation("Cards"); }); + modelBuilder.Entity("SVSim.Database.Models.SleeveShopSeriesEntry", b => + { + b.Navigation("Products"); + }); + modelBuilder.Entity("SVSim.Database.Models.Viewer", b => { b.Navigation("Achievements"); diff --git a/SVSim.Database/Models/SleeveShopProductEntry.cs b/SVSim.Database/Models/SleeveShopProductEntry.cs new file mode 100644 index 0000000..bdb1387 --- /dev/null +++ b/SVSim.Database/Models/SleeveShopProductEntry.cs @@ -0,0 +1,32 @@ +using SVSim.Database.Common; + +namespace SVSim.Database.Models; + +/// +/// One purchasable sleeve product. PK = wire product_id (e.g. 301901). FK SeriesId. +/// +/// Both and are nullable. At least one must be +/// populated for an enabled product (both zero = free, both null = invalid). Sleeves don't have +/// the two-tier intro/regular pricing that BuildDeck products use — one price per currency. +/// +/// +/// drives both the catalog display (in /sleeve/info) and the actual grant +/// list (in /sleeve/buy). The capture shows each sleeve product grants a sleeve (type=6) and an +/// emblem (type=7) — both faithful reward_detail_ids that exist in the cosmetic catalogs. +/// +/// +public class SleeveShopProductEntry : BaseEntity +{ + public int SeriesId { get; set; } + /// Wire `name` field — SystemText key like "sleeve_138". Localised client-side. + public string NameKey { get; set; } = string.Empty; + + public int? PriceCrystal { get; set; } + public int? PriceRupy { get; set; } + + public bool IsEnabled { get; set; } + + public List Rewards { get; set; } = new(); + + public SleeveShopSeriesEntry? Series { get; set; } +} diff --git a/SVSim.Database/Models/SleeveShopProductRewardEntry.cs b/SVSim.Database/Models/SleeveShopProductRewardEntry.cs new file mode 100644 index 0000000..5e7d1da --- /dev/null +++ b/SVSim.Database/Models/SleeveShopProductRewardEntry.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; + +namespace SVSim.Database.Models; + +/// +/// One per-buy reward attached to a sleeve product. Owned by . +/// Wire shape: one entry of the product-level `rewards` array in /sleeve/info. Order is +/// preserved by since the wire shape is an ordered array, not a dict. +/// +[Owned] +public class SleeveShopProductRewardEntry +{ + public int OrderIndex { get; set; } + public int RewardType { get; set; } // Wizard.UserGoods.Type + public long RewardDetailId { get; set; } + public int RewardNumber { get; set; } +} diff --git a/SVSim.Database/Models/SleeveShopSeriesEntry.cs b/SVSim.Database/Models/SleeveShopSeriesEntry.cs new file mode 100644 index 0000000..f024100 --- /dev/null +++ b/SVSim.Database/Models/SleeveShopSeriesEntry.cs @@ -0,0 +1,16 @@ +using SVSim.Database.Common; + +namespace SVSim.Database.Models; + +/// +/// One sleeve-shop series (a themed collection — e.g. series 3019 "BattlePass sleeves", +/// series 3004 "Granblue Fantasy collab"). PK = wire series_id. IsEnabled gates whether +/// /sleeve/info renders this series. +/// +public class SleeveShopSeriesEntry : BaseEntity +{ + public bool IsNew { get; set; } + public bool IsEnabled { get; set; } + + public List Products { get; set; } = new(); +} diff --git a/SVSim.Database/SVSimDbContext.cs b/SVSim.Database/SVSimDbContext.cs index 0796f95..2a5ae17 100644 --- a/SVSim.Database/SVSimDbContext.cs +++ b/SVSim.Database/SVSimDbContext.cs @@ -70,6 +70,8 @@ public class SVSimDbContext : DbContext public DbSet Packs => Set(); public DbSet BuildDeckSeries => Set(); public DbSet BuildDeckProducts => Set(); + public DbSet SleeveShopSeries => Set(); + public DbSet SleeveShopProducts => Set(); public DbSet MaintenanceCards => Set(); public DbSet FeatureMaintenances => Set(); public DbSet PreReleaseInfos => Set(); @@ -180,6 +182,14 @@ public class SVSimDbContext : DbContext modelBuilder.Entity().HasIndex(p => p.SeriesId); + modelBuilder.Entity().OwnsMany(p => p.Rewards); + modelBuilder.Entity() + .HasOne(p => p.Series) + .WithMany(s => s.Products) + .HasForeignKey(p => p.SeriesId) + .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasIndex(p => p.SeriesId); + modelBuilder.Entity(b => { b.HasKey(r => new { r.CardId, r.Type, r.CosmeticId }); diff --git a/SVSim.EmulatedEntrypoint/Controllers/SleeveController.cs b/SVSim.EmulatedEntrypoint/Controllers/SleeveController.cs new file mode 100644 index 0000000..0da5b50 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Controllers/SleeveController.cs @@ -0,0 +1,189 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using SVSim.Database; +using SVSim.Database.Enums; +using SVSim.Database.Models; +using SVSim.Database.Services; +using SVSim.EmulatedEntrypoint.Models.Dtos; +using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Sleeve; +using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Sleeve; + +namespace SVSim.EmulatedEntrypoint.Controllers; + +/// +/// /sleeve/* — the sleeve shop. Catalog + single-product purchase. No series-completion bonus +/// (sleeves are sold individually; the leader-skin shop is the family with set-buys). +/// +[Route("sleeve")] +public class SleeveController : SVSimController +{ + private readonly SVSimDbContext _db; + private readonly RewardGrantService _rewards; + + public SleeveController(SVSimDbContext db, RewardGrantService rewards) + { + _db = db; + _rewards = rewards; + } + + [HttpPost("info")] + public async Task> Info() + { + if (!TryGetViewerId(out long viewerId)) return Unauthorized(); + + // is_purchased_product is "viewer owns at least one sleeve granted by this product". + // Loading the viewer's sleeve-id set once and checking each product against it avoids + // an N+1 over products. + var ownedSleeveIds = (await _db.Viewers + .Where(v => v.Id == viewerId) + .SelectMany(v => v.Sleeves.Select(s => (long)s.Id)) + .ToListAsync()).ToHashSet(); + + var series = await _db.SleeveShopSeries + .Where(s => s.IsEnabled) + .Include(s => s.Products.Where(p => p.IsEnabled)).ThenInclude(p => p.Rewards) + .OrderBy(s => s.Id) + .ToListAsync(); + + var sleeveList = new Dictionary(); + foreach (var s in series) + { + var products = new Dictionary(); + foreach (var p in s.Products.OrderBy(p => p.Id)) + { + products[p.Id.ToString()] = new SleeveProductDto + { + ProductId = p.Id, + Name = p.NameKey, + PriceCrystal = p.PriceCrystal, + PriceRupy = p.PriceRupy, + IsPurchasedProduct = IsProductPurchased(p, ownedSleeveIds), + Rewards = p.Rewards.OrderBy(r => r.OrderIndex).Select(r => new SleeveProductRewardDto + { + RewardType = r.RewardType, + RewardDetailId = r.RewardDetailId, + RewardNumber = r.RewardNumber, + }).ToList(), + }; + } + + sleeveList[s.Id.ToString()] = new SleeveSeriesDto + { + SeriesId = s.Id, + IsNew = s.IsNew, + ProductInfo = products, + }; + } + + return new SleeveInfoResponse { SleeveList = sleeveList }; + } + + [HttpPost("buy")] + public async Task> Buy(SleeveBuyRequest request) + { + if (!TryGetViewerId(out long viewerId)) return Unauthorized(); + + if (request.SalesType is 3) + return StatusCode(StatusCodes.Status501NotImplemented, + new { error = "ticket_currency_path_not_implemented" }); + if (request.SalesType is < 0 or > 3) + return BadRequest(new { error = "invalid_sales_type" }); + + var product = await _db.SleeveShopProducts + .Include(p => p.Rewards) + .Include(p => p.Series) + .FirstOrDefaultAsync(p => p.Id == request.ProductId); + if (product is null) return NotFound(new { error = "unknown_product" }); + + if (!product.IsEnabled || product.Series is not { IsEnabled: true }) + return BadRequest(new { error = "product_not_available" }); + + // Defence-in-depth: client also sends series_id; reject mismatches so a misencoded + // request can't accidentally bypass per-series state we'll later add (e.g. series-new flag). + if (product.SeriesId != request.SeriesId) + return BadRequest(new { error = "series_product_mismatch" }); + + var viewer = await LoadViewerGraphAsync(viewerId); + + if (IsProductPurchased(product, viewer.Sleeves.Select(s => (long)s.Id).ToHashSet())) + return BadRequest(new { error = "already_purchased" }); + + // Pricing: capture-confirmed shape is single-price-per-currency (no intro/regular tiers + // like BuildDeck). At least one of crystal/rupy must match the chosen sales_type; + // sales_type==0 means "free", which requires both prices == 0. + var rewardList = new List(); + switch (request.SalesType) + { + case 0: // free + if (!(product.PriceCrystal == 0 && product.PriceRupy == 0)) + return BadRequest(new { error = "price_not_available_for_currency" }); + break; + case 1: // crystal + if (product.PriceCrystal is null) + return BadRequest(new { error = "price_not_available_for_currency" }); + var crystalCost = (ulong)product.PriceCrystal.Value; + if (viewer.Currency.Crystals < crystalCost) + return BadRequest(new { error = "insufficient_crystals" }); + viewer.Currency.Crystals -= crystalCost; + rewardList.Add(new RewardListEntry { RewardType = 2, RewardId = 0, RewardNum = (int)viewer.Currency.Crystals }); + break; + case 2: // rupy + if (product.PriceRupy is null) + return BadRequest(new { error = "price_not_available_for_currency" }); + var rupyCost = (ulong)product.PriceRupy.Value; + if (viewer.Currency.Rupees < rupyCost) + return BadRequest(new { error = "insufficient_rupees" }); + viewer.Currency.Rupees -= rupyCost; + rewardList.Add(new RewardListEntry { RewardType = 9, RewardId = 0, RewardNum = (int)viewer.Currency.Rupees }); + break; + } + + // Grant each catalog reward through the central dispatcher — covers sleeve (6), emblem + // (7), and any future bundled grants. ApplyAsync returns post-state-aware reward entries + // suitable for emission as-is. + foreach (var r in product.Rewards.OrderBy(r => r.OrderIndex)) + { + var granted = await _rewards.ApplyAsync(viewer, (UserGoodsType)r.RewardType, r.RewardDetailId, r.RewardNumber); + foreach (var g in granted) + { + rewardList.Add(new RewardListEntry + { + RewardType = g.RewardType, + RewardId = g.RewardId, + RewardNum = g.RewardNum, + }); + } + } + + await _db.SaveChangesAsync(); + + return new SleeveBuyResponse { RewardList = rewardList }; + } + + /// + /// A product is "purchased" once the viewer owns at least one of its sleeve-typed reward + /// grants. Emblem/other grants aren't load-bearing for this check — a viewer who somehow + /// ended up with the emblem but not the sleeve (e.g. partial gift) should still be allowed + /// to buy the product to pick up the sleeve. + /// + private static bool IsProductPurchased(SleeveShopProductEntry product, HashSet ownedSleeveIds) + { + foreach (var r in product.Rewards) + { + if (r.RewardType == (int)UserGoodsType.Sleeve && ownedSleeveIds.Contains(r.RewardDetailId)) + return true; + } + return false; + } + + private Task LoadViewerGraphAsync(long viewerId) => _db.Viewers + .Include(v => v.Sleeves) + .Include(v => v.Emblems) + .Include(v => v.LeaderSkins) + .Include(v => v.Degrees) + .Include(v => v.MyPageBackgrounds) + .Include(v => v.Items).ThenInclude(i => i.Item) + .Include(v => v.Cards).ThenInclude(c => c.Card) + .AsSplitQuery() + .FirstAsync(v => v.Id == viewerId); +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Sleeve/SleeveBuyRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Sleeve/SleeveBuyRequest.cs new file mode 100644 index 0000000..042035e --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/Sleeve/SleeveBuyRequest.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Sleeve; + +/// +/// /sleeve/buy request body. sales_type is ShopCommonUtility.SalesType: +/// 0=free, 1=crystal, 2=rupy, 3=ticket (v1: 3 returns 501, no ticket-priced sleeve captured). +/// +[MessagePackObject] +public class SleeveBuyRequest : BaseRequest +{ + [JsonPropertyName("series_id")] + [Key("series_id")] + public int SeriesId { get; set; } + + [JsonPropertyName("product_id")] + [Key("product_id")] + public int ProductId { get; set; } + + [JsonPropertyName("sales_type")] + [Key("sales_type")] + public int SalesType { get; set; } +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveBuyResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveBuyResponse.cs new file mode 100644 index 0000000..a077dea --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveBuyResponse.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Sleeve; + +/// +/// /sleeve/buy response. reward_list items use reward_id/reward_num +/// (POST-STATE-TOTAL for currencies, grant id+count for cosmetics) — driven by +/// PlayerStaticData.UpdateHaveUserGoodsNumByJsonData. Mirrors the /pack/open + +/// /build_deck/buy reward_list semantics. +/// +[MessagePackObject] +public class SleeveBuyResponse +{ + [JsonPropertyName("reward_list")] + [Key("reward_list")] + public List RewardList { get; set; } = new(); +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveInfoResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveInfoResponse.cs new file mode 100644 index 0000000..1fb8d11 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Sleeve/SleeveInfoResponse.cs @@ -0,0 +1,84 @@ +using System.Text.Json.Serialization; +using MessagePack; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Sleeve; + +/// +/// /sleeve/info response. Wire shape: {sleeve_list: {: SleeveSeriesDto}}. +/// Dict-keyed (not array) to match the prod capture exactly — LitJson's numeric indexer in +/// SleevePurchaseInfoTask.Parse() iterates dict values by inserted order, so either +/// shape would work, but mirroring the wire avoids surprise. +/// +[MessagePackObject] +public class SleeveInfoResponse +{ + [JsonPropertyName("sleeve_list")] + [Key("sleeve_list")] + public Dictionary SleeveList { get; set; } = new(); +} + +[MessagePackObject] +public class SleeveSeriesDto +{ + [JsonPropertyName("series_id")] + [Key("series_id")] + public int SeriesId { get; set; } + + [JsonPropertyName("is_new")] + [Key("is_new")] + public bool IsNew { get; set; } + + /// Dict keyed by product_id string — same iteration convention as sleeve_list. + [JsonPropertyName("product_info")] + [Key("product_info")] + public Dictionary ProductInfo { get; set; } = new(); +} + +[MessagePackObject] +public class SleeveProductDto +{ + [JsonPropertyName("product_id")] + [Key("product_id")] + public int ProductId { get; set; } + + /// SystemText key (e.g. "sleeve_138") — client resolves via GetSleeveProductText. + [JsonPropertyName("name")] + [Key("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("rewards")] + [Key("rewards")] + public List Rewards { get; set; } = new(); + + [JsonPropertyName("sales_period_info")] + [Key("sales_period_info")] + public List SalesPeriodInfo { get; set; } = new(); // always [] in v1 + + [JsonPropertyName("is_purchased_product")] + [Key("is_purchased_product")] + public bool IsPurchasedProduct { get; set; } + + [JsonPropertyName("price_crystal")] + [Key("price_crystal")] + public int? PriceCrystal { get; set; } + + [JsonPropertyName("price_rupy")] + [Key("price_rupy")] + public int? PriceRupy { get; set; } +} + +[MessagePackObject] +public class SleeveProductRewardDto +{ + [JsonPropertyName("reward_type")] + [Key("reward_type")] + public int RewardType { get; set; } + + [JsonPropertyName("reward_detail_id")] + [Key("reward_detail_id")] + public long RewardDetailId { get; set; } + + [JsonPropertyName("reward_number")] + [Key("reward_number")] + public int RewardNumber { get; set; } +} diff --git a/SVSim.UnitTests/Controllers/SleeveControllerTests.cs b/SVSim.UnitTests/Controllers/SleeveControllerTests.cs new file mode 100644 index 0000000..70068a8 --- /dev/null +++ b/SVSim.UnitTests/Controllers/SleeveControllerTests.cs @@ -0,0 +1,217 @@ +using System.Net; +using System.Text; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using SVSim.Database; +using SVSim.Database.Models; +using SVSim.UnitTests.Infrastructure; + +namespace SVSim.UnitTests.Controllers; + +public class SleeveControllerTests +{ + private static StringContent JsonBody(string json) => new(json, Encoding.UTF8, "application/json"); + + /// + /// Seeds: series 9001 (enabled) with one crystal-priced product 900101 granting + /// sleeve 9000011 + emblem 9000011. Caller sets viewer crystals. + /// Sleeve + emblem catalog rows are inserted with placeholder names so RewardGrantService + /// can resolve them. + /// + private static async Task SeedCrystalProduct(SVSimTestFactory f, long viewerId, ulong crystals) + { + using var scope = f.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + // Sleeve + emblem catalog must exist so RewardGrantService.ApplyAsync can find them. + // Using ids outside the 1k-1.6k cosmetic seed range so they can't collide with reference data. + const int testSleeveId = 9000011; + const int testEmblemId = 9000011; + if (!await db.Sleeves.AnyAsync(s => s.Id == testSleeveId)) + db.Sleeves.Add(new SleeveEntry { Id = testSleeveId }); + if (!await db.Emblems.AnyAsync(e => e.Id == testEmblemId)) + db.Emblems.Add(new EmblemEntry { Id = testEmblemId }); + + db.SleeveShopSeries.Add(new SleeveShopSeriesEntry + { + Id = 9001, IsEnabled = true, IsNew = false, + Products = + { + new SleeveShopProductEntry + { + Id = 900101, SeriesId = 9001, NameKey = "sleeve_test", PriceCrystal = 400, + IsEnabled = true, + Rewards = + { + new SleeveShopProductRewardEntry { OrderIndex = 0, RewardType = 7, RewardDetailId = testEmblemId, RewardNumber = 1 }, + new SleeveShopProductRewardEntry { OrderIndex = 1, RewardType = 6, RewardDetailId = testSleeveId, RewardNumber = 1 }, + }, + }, + }, + }); + + var v = await db.Viewers.FirstAsync(x => x.Id == viewerId); + v.Currency.Crystals = crystals; + await db.SaveChangesAsync(); + } + + [Test] + public async Task Info_returns_dict_keyed_by_series_id_and_product_id() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 0); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/sleeve/info", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""")); + + var body = await response.Content.ReadAsStringAsync(); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body); + + using var doc = JsonDocument.Parse(body); + var sleeveList = doc.RootElement.GetProperty("sleeve_list"); + Assert.That(sleeveList.ValueKind, Is.EqualTo(JsonValueKind.Object), "wire shape is dict-keyed by series_id string"); + + var series = sleeveList.GetProperty("9001"); + Assert.That(series.GetProperty("series_id").GetInt32(), Is.EqualTo(9001)); + + var productInfo = series.GetProperty("product_info"); + Assert.That(productInfo.ValueKind, Is.EqualTo(JsonValueKind.Object), "product_info is dict-keyed by product_id string"); + + var product = productInfo.GetProperty("900101"); + Assert.That(product.GetProperty("product_id").GetInt32(), Is.EqualTo(900101)); + Assert.That(product.GetProperty("name").GetString(), Is.EqualTo("sleeve_test")); + Assert.That(product.GetProperty("price_crystal").GetInt32(), Is.EqualTo(400)); + Assert.That(product.GetProperty("is_purchased_product").GetBoolean(), Is.False); + Assert.That(product.GetProperty("rewards").GetArrayLength(), Is.EqualTo(2)); + } + + [Test] + public async Task Buy_with_crystals_debits_currency_and_grants_cosmetics() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/sleeve/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"product_id":900101,"sales_type":1}""")); + + var body = await response.Content.ReadAsStringAsync(); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body); + + using var doc = JsonDocument.Parse(body); + var rewardList = doc.RootElement.GetProperty("reward_list"); + Assert.That(rewardList.GetArrayLength(), Is.EqualTo(3)); // crystal post-state + emblem + sleeve + + // First entry: crystal balance post-debit. reward_type=2 (Crystal), reward_id=0, num=600 (1000-400). + var crystal = rewardList[0]; + Assert.That(crystal.GetProperty("reward_type").GetInt32(), Is.EqualTo(2)); + Assert.That(crystal.GetProperty("reward_id").GetInt64(), Is.EqualTo(0)); + Assert.That(crystal.GetProperty("reward_num").GetInt32(), Is.EqualTo(600)); + + // Viewer state: crystals decremented; sleeve + emblem in owned collections. + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var viewer = await db.Viewers + .Include(v => v.Sleeves) + .Include(v => v.Emblems) + .FirstAsync(v => v.Id == viewerId); + Assert.That(viewer.Currency.Crystals, Is.EqualTo(600UL)); + Assert.That(viewer.Sleeves.Any(s => s.Id == 9000011), Is.True); + Assert.That(viewer.Emblems.Any(e => e.Id == 9000011), Is.True); + } + + [Test] + public async Task Buy_with_insufficient_crystals_rejects_with_400() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 100); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/sleeve/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"product_id":900101,"sales_type":1}""")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Buy_with_series_product_mismatch_rejects_with_400() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + // product 900101 is in series 9001, not 9999 + var response = await client.PostAsync("/sleeve/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9999,"product_id":900101,"sales_type":1}""")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Buy_already_purchased_sleeve_rejects_with_400() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 1000); + + // First buy succeeds + using var client = factory.CreateAuthenticatedClient(viewerId); + var first = await client.PostAsync("/sleeve/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"product_id":900101,"sales_type":1}""")); + Assert.That(first.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + + // Second buy rejected + var second = await client.PostAsync("/sleeve/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"product_id":900101,"sales_type":1}""")); + Assert.That(second.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Buy_ticket_sales_type_returns_501() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/sleeve/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"product_id":900101,"sales_type":3}""")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotImplemented)); + } + + [Test] + public async Task Info_marks_already_owned_sleeve_as_purchased() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedCrystalProduct(factory, viewerId, crystals: 1000); + + // Pre-grant the sleeve so /info should flag is_purchased_product=true + using (var scope = factory.Services.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + var viewer = await db.Viewers.Include(v => v.Sleeves).FirstAsync(v => v.Id == viewerId); + var sleeve = await db.Sleeves.FindAsync(9000011); + viewer.Sleeves.Add(sleeve!); + await db.SaveChangesAsync(); + } + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/sleeve/info", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""")); + + var body = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(body); + var product = doc.RootElement + .GetProperty("sleeve_list").GetProperty("9001") + .GetProperty("product_info").GetProperty("900101"); + Assert.That(product.GetProperty("is_purchased_product").GetBoolean(), Is.True); + } +} diff --git a/SVSim.UnitTests/Importers/SleeveShopImporterTests.cs b/SVSim.UnitTests/Importers/SleeveShopImporterTests.cs new file mode 100644 index 0000000..7fc9313 --- /dev/null +++ b/SVSim.UnitTests/Importers/SleeveShopImporterTests.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using SVSim.Bootstrap.Importers; +using SVSim.Database; +using SVSim.Database.Models; +using SVSim.UnitTests.Infrastructure; + +namespace SVSim.UnitTests.Importers; + +public class SleeveShopImporterTests +{ + private static string SeedDir => Path.Combine(AppContext.BaseDirectory, "Data", "seeds"); + + [Test] + public async Task Imports_series_and_products_from_seed_file() + { + using var factory = new SVSimTestFactory(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + await new SleeveShopImporter().ImportAsync(db, SeedDir); + + var series = await db.SleeveShopSeries + .Include(s => s.Products).ThenInclude(p => p.Rewards) + .OrderBy(s => s.Id) + .ToListAsync(); + + Assert.That(series.Count, Is.GreaterThan(0), "seed file should contain series"); + // Spot-check series 3019 (BattlePass sleeves) — captured at 6 products with crystal pricing. + var bp = series.FirstOrDefault(s => s.Id == 3019); + Assert.That(bp, Is.Not.Null, "series 3019 should be present"); + Assert.That(bp!.Products.Count, Is.GreaterThan(0)); + + var firstProduct = bp.Products.OrderBy(p => p.Id).First(); + Assert.That(firstProduct.NameKey, Does.StartWith("sleeve_"), "name should be a SystemText key"); + Assert.That(firstProduct.Rewards, Is.Not.Empty, "products should have catalog rewards"); + Assert.That(firstProduct.IsEnabled, Is.True); + } + + [Test] + public async Task Is_idempotent_on_rerun() + { + using var factory = new SVSimTestFactory(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + await new SleeveShopImporter().ImportAsync(db, SeedDir); + int seriesBefore = await db.SleeveShopSeries.CountAsync(); + int productsBefore = await db.SleeveShopProducts.CountAsync(); + + await new SleeveShopImporter().ImportAsync(db, SeedDir); + + Assert.That(await db.SleeveShopSeries.CountAsync(), Is.EqualTo(seriesBefore)); + Assert.That(await db.SleeveShopProducts.CountAsync(), Is.EqualTo(productsBefore)); + } + + [Test] + public async Task Replaces_rewards_wholesale_on_rerun() + { + // Owned rewards collection: importer clears and re-adds. A stale catalog reward should + // not survive a re-import. (Hand-tamper one row, re-import, check the tamper is gone.) + using var factory = new SVSimTestFactory(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + await new SleeveShopImporter().ImportAsync(db, SeedDir); + var product = await db.SleeveShopProducts + .Include(p => p.Rewards) + .OrderBy(p => p.Id) + .FirstAsync(); + + int originalCount = product.Rewards.Count; + product.Rewards.Add(new SleeveShopProductRewardEntry + { + OrderIndex = 99, RewardType = 99, RewardDetailId = 99, RewardNumber = 99, + }); + await db.SaveChangesAsync(); + + await new SleeveShopImporter().ImportAsync(db, SeedDir); + + var reloaded = await db.SleeveShopProducts + .Include(p => p.Rewards) + .FirstAsync(p => p.Id == product.Id); + Assert.That(reloaded.Rewards.Count, Is.EqualTo(originalCount), "extra reward should be wiped on re-import"); + Assert.That(reloaded.Rewards.Any(r => r.RewardType == 99), Is.False); + } +} diff --git a/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs b/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs index b84fd3f..b9884fd 100644 --- a/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs +++ b/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs @@ -209,6 +209,7 @@ internal sealed class SVSimTestFactory : WebApplicationFactory await new PracticeOpponentImporter().ImportAsync(ctx, seedDir); await new PaymentItemImporter().ImportAsync(ctx, seedDir); await new ItemImporter().ImportAsync(ctx, seedDir); + await new SleeveShopImporter().ImportAsync(ctx, seedDir); var puzzleImporter = new PuzzleImporter(); await puzzleImporter.ImportGroupsAsync(ctx, seedDir); await puzzleImporter.ImportPuzzlesAsync(ctx, seedDir);