diff --git a/SVSim.Bootstrap/Data/seeds/leader-skin-shop.json b/SVSim.Bootstrap/Data/seeds/leader-skin-shop.json new file mode 100644 index 0000000..c91b513 --- /dev/null +++ b/SVSim.Bootstrap/Data/seeds/leader-skin-shop.json @@ -0,0 +1,4053 @@ +[ + { + "series_id": 100, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2000, + "set_price_rupy": 2000, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 1, + "leader_skin_id": 101, + "product_name_key": "LSPPN_神撃のバハムート_1", + "introduction_key": "LSPI_神撃のバハムート_1", + "cv_name_key": "LSPCN_神撃のバハムート_1", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 101, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010101, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001001, + "reward_number": 1 + } + ] + }, + { + "product_id": 2, + "leader_skin_id": 102, + "product_name_key": "LSPPN_神撃のバハムート_2", + "introduction_key": "LSPI_神撃のバハムート_2", + "cv_name_key": "LSPCN_神撃のバハムート_2", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 102, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010201, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001002, + "reward_number": 1 + } + ] + }, + { + "product_id": 3, + "leader_skin_id": 103, + "product_name_key": "LSPPN_神撃のバハムート_3", + "introduction_key": "LSPI_神撃のバハムート_3", + "cv_name_key": "LSPCN_神撃のバハムート_3", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 103, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010301, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001003, + "reward_number": 1 + } + ] + }, + { + "product_id": 4, + "leader_skin_id": 104, + "product_name_key": "LSPPN_神撃のバハムート_4", + "introduction_key": "LSPI_神撃のバハムート_4", + "cv_name_key": "LSPCN_神撃のバハムート_4", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 104, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010401, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001004, + "reward_number": 1 + } + ] + }, + { + "product_id": 5, + "leader_skin_id": 105, + "product_name_key": "LSPPN_神撃のバハムート_5", + "introduction_key": "LSPI_神撃のバハムート_5", + "cv_name_key": "LSPCN_神撃のバハムート_5", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 105, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010501, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001005, + "reward_number": 1 + } + ] + }, + { + "product_id": 6, + "leader_skin_id": 106, + "product_name_key": "LSPPN_神撃のバハムート_6", + "introduction_key": "LSPI_神撃のバハムート_6", + "cv_name_key": "LSPCN_神撃のバハムート_6", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 106, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010601, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001006, + "reward_number": 1 + } + ] + }, + { + "product_id": 7, + "leader_skin_id": 107, + "product_name_key": "LSPPN_神撃のバハムート_7", + "introduction_key": "LSPI_神撃のバハムート_7", + "cv_name_key": "LSPCN_神撃のバハムート_7", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 107, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100010701, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3001007, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 101, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2800, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 8, + "leader_skin_id": 201, + "product_name_key": "LSPPN_SF5_1", + "introduction_key": "LSPI_SF5_1", + "cv_name_key": "LSPCN_SF5_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 201, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000101, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008002, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301201, + "reward_number": 1 + } + ] + }, + { + "product_id": 9, + "leader_skin_id": 202, + "product_name_key": "LSPPN_SF5_2", + "introduction_key": "LSPI_SF5_2", + "cv_name_key": "LSPCN_SF5_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 202, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000102, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008003, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301202, + "reward_number": 1 + } + ] + }, + { + "product_id": 10, + "leader_skin_id": 203, + "product_name_key": "LSPPN_SF5_3", + "introduction_key": "LSPI_SF5_3", + "cv_name_key": "LSPCN_SF5_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 203, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000103, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008004, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301203, + "reward_number": 1 + } + ] + }, + { + "product_id": 11, + "leader_skin_id": 204, + "product_name_key": "LSPPN_SF5_4", + "introduction_key": "LSPI_SF5_4", + "cv_name_key": "LSPCN_SF5_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 204, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000104, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008005, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301204, + "reward_number": 1 + } + ] + }, + { + "product_id": 12, + "leader_skin_id": 205, + "product_name_key": "LSPPN_SF5_5", + "introduction_key": "LSPI_SF5_5", + "cv_name_key": "LSPCN_SF5_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 205, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000105, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008006, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301205, + "reward_number": 1 + } + ] + }, + { + "product_id": 13, + "leader_skin_id": 206, + "product_name_key": "LSPPN_SF5_6", + "introduction_key": "LSPI_SF5_6", + "cv_name_key": "LSPCN_SF5_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 206, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000106, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008007, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301206, + "reward_number": 1 + } + ] + }, + { + "product_id": 14, + "leader_skin_id": 207, + "product_name_key": "LSPPN_SF5_7", + "introduction_key": "LSPI_SF5_7", + "cv_name_key": "LSPCN_SF5_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 207, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000107, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3008008, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301207, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 103, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 3200, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 7, + "reward_detail_id": 400000600, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 6, + "reward_detail_id": 3016001, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 22, + "leader_skin_id": 701, + "product_name_key": "LSPPN_グランブルーファンタジー_1", + "introduction_key": "LSPI_グランブルーファンタジー_1", + "cv_name_key": "LSPCN_グランブルーファンタジー_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 701, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000601, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016002, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301401, + "reward_number": 1 + } + ] + }, + { + "product_id": 23, + "leader_skin_id": 702, + "product_name_key": "LSPPN_グランブルーファンタジー_2", + "introduction_key": "LSPI_グランブルーファンタジー_2", + "cv_name_key": "LSPCN_グランブルーファンタジー_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 702, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000602, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016003, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301402, + "reward_number": 1 + } + ] + }, + { + "product_id": 24, + "leader_skin_id": 703, + "product_name_key": "LSPPN_グランブルーファンタジー_3", + "introduction_key": "LSPI_グランブルーファンタジー_3", + "cv_name_key": "LSPCN_グランブルーファンタジー_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 703, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000603, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016004, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301403, + "reward_number": 1 + } + ] + }, + { + "product_id": 25, + "leader_skin_id": 704, + "product_name_key": "LSPPN_グランブルーファンタジー_4", + "introduction_key": "LSPI_グランブルーファンタジー_4", + "cv_name_key": "LSPCN_グランブルーファンタジー_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 704, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000604, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016005, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301404, + "reward_number": 1 + } + ] + }, + { + "product_id": 26, + "leader_skin_id": 705, + "product_name_key": "LSPPN_グランブルーファンタジー_5", + "introduction_key": "LSPI_グランブルーファンタジー_5", + "cv_name_key": "LSPCN_グランブルーファンタジー_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 705, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000605, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016006, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301405, + "reward_number": 1 + } + ] + }, + { + "product_id": 27, + "leader_skin_id": 706, + "product_name_key": "LSPPN_グランブルーファンタジー_6", + "introduction_key": "LSPI_グランブルーファンタジー_6", + "cv_name_key": "LSPCN_グランブルーファンタジー_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 706, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000606, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016007, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301406, + "reward_number": 1 + } + ] + }, + { + "product_id": 28, + "leader_skin_id": 707, + "product_name_key": "LSPPN_グランブルーファンタジー_7", + "introduction_key": "LSPI_グランブルーファンタジー_7", + "cv_name_key": "LSPCN_グランブルーファンタジー_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 707, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000607, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016008, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301407, + "reward_number": 1 + } + ] + }, + { + "product_id": 29, + "leader_skin_id": 708, + "product_name_key": "LSPPN_グランブルーファンタジー_8", + "introduction_key": "LSPI_グランブルーファンタジー_8", + "cv_name_key": "LSPCN_グランブルーファンタジー_8", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 708, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000608, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3016009, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301408, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 104, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 500, + "set_price_rupy": 500, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 30, + "leader_skin_id": 601, + "product_name_key": "LSPPN_NetEase_1", + "introduction_key": "LSPI_NetEase_1", + "cv_name_key": "LSPCN_NetEase_1", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 601, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000801, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3019001, + "reward_number": 1 + } + ] + }, + { + "product_id": 31, + "leader_skin_id": 603, + "product_name_key": "LSPPN_NetEase_2", + "introduction_key": "LSPI_NetEase_2", + "cv_name_key": "LSPCN_NetEase_2", + "single_price_crystal": 500, + "single_price_rupy": 500, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 603, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400000803, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3019003, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 106, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 1200, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3032002, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 40, + "leader_skin_id": 1001, + "product_name_key": "LSPPN_プリコネ_1", + "introduction_key": "LSPI_プリコネ_1", + "cv_name_key": "LSPCN_プリコネ_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1001, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400001302, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3032003, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301601, + "reward_number": 1 + } + ] + }, + { + "product_id": 41, + "leader_skin_id": 1002, + "product_name_key": "LSPPN_プリコネ_2", + "introduction_key": "LSPI_プリコネ_2", + "cv_name_key": "LSPCN_プリコネ_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1002, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400001303, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3032004, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301602, + "reward_number": 1 + } + ] + }, + { + "product_id": 42, + "leader_skin_id": 1003, + "product_name_key": "LSPPN_プリコネ_3", + "introduction_key": "LSPI_プリコネ_3", + "cv_name_key": "LSPCN_プリコネ_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1003, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400001304, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3032005, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 301603, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 109, + "is_new": false, + "set_sales_status": 0, + "set_price_crystal": null, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 58, + "leader_skin_id": 1203, + "product_name_key": "LSPPN_マナリアフレンズ_1", + "introduction_key": "LSPI_マナリアフレンズ_1", + "cv_name_key": "LSPCN_マナリアフレンズ_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1203, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 112, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2000, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3043002, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 62, + "leader_skin_id": 1804, + "product_name_key": "LSPPN_プリコネ2_1", + "introduction_key": "LSPI_プリコネ2_1", + "cv_name_key": "LSPCN_プリコネ2_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1804, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002002, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3043003, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302101, + "reward_number": 1 + } + ] + }, + { + "product_id": 63, + "leader_skin_id": 1815, + "product_name_key": "LSPPN_プリコネ2_2", + "introduction_key": "LSPI_プリコネ2_2", + "cv_name_key": "LSPCN_プリコネ2_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1815, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002003, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3043004, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302102, + "reward_number": 1 + } + ] + }, + { + "product_id": 64, + "leader_skin_id": 1806, + "product_name_key": "LSPPN_プリコネ2_3", + "introduction_key": "LSPI_プリコネ2_3", + "cv_name_key": "LSPCN_プリコネ2_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1806, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002004, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3043005, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302103, + "reward_number": 1 + } + ] + }, + { + "product_id": 65, + "leader_skin_id": 1807, + "product_name_key": "LSPPN_プリコネ2_4", + "introduction_key": "LSPI_プリコネ2_4", + "cv_name_key": "LSPCN_プリコネ2_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1807, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002005, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3043006, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302104, + "reward_number": 1 + } + ] + }, + { + "product_id": 66, + "leader_skin_id": 1808, + "product_name_key": "LSPPN_プリコネ2_5", + "introduction_key": "LSPI_プリコネ2_5", + "cv_name_key": "LSPCN_プリコネ2_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1808, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002006, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3043007, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302105, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 115, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 3200, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 78, + "leader_skin_id": 1901, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_1", + "introduction_key": "LSPI_Shadowverse新リーダースキン_1", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1901, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000106, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000081, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302401, + "reward_number": 1 + } + ] + }, + { + "product_id": 79, + "leader_skin_id": 1902, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_2", + "introduction_key": "LSPI_Shadowverse新リーダースキン_2", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1902, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000206, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000082, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302402, + "reward_number": 1 + } + ] + }, + { + "product_id": 80, + "leader_skin_id": 1903, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_3", + "introduction_key": "LSPI_Shadowverse新リーダースキン_3", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1903, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000306, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000083, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302403, + "reward_number": 1 + } + ] + }, + { + "product_id": 81, + "leader_skin_id": 1904, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_4", + "introduction_key": "LSPI_Shadowverse新リーダースキン_4", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1904, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000406, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000084, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302404, + "reward_number": 1 + } + ] + }, + { + "product_id": 82, + "leader_skin_id": 1905, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_5", + "introduction_key": "LSPI_Shadowverse新リーダースキン_5", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1905, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000506, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000085, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302405, + "reward_number": 1 + } + ] + }, + { + "product_id": 83, + "leader_skin_id": 1906, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_6", + "introduction_key": "LSPI_Shadowverse新リーダースキン_6", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1906, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000606, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000086, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302406, + "reward_number": 1 + } + ] + }, + { + "product_id": 84, + "leader_skin_id": 1907, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_7", + "introduction_key": "LSPI_Shadowverse新リーダースキン_7", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1907, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000706, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000087, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302407, + "reward_number": 1 + } + ] + }, + { + "product_id": 85, + "leader_skin_id": 1908, + "product_name_key": "LSPPN_Shadowverse新リーダースキン_8", + "introduction_key": "LSPI_Shadowverse新リーダースキン_8", + "cv_name_key": "LSPCN_Shadowverse新リーダースキン_8", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 1908, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 100000806, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 3000088, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302408, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 117, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2800, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3040003, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 94, + "leader_skin_id": 2701, + "product_name_key": "LSPPN_チャンピオンズバトル_1", + "introduction_key": "LSPI_チャンピオンズバトル_1", + "cv_name_key": "LSPCN_チャンピオンズバトル_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2701, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002701, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713114010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302601, + "reward_number": 1 + } + ] + }, + { + "product_id": 95, + "leader_skin_id": 2702, + "product_name_key": "LSPPN_チャンピオンズバトル_2", + "introduction_key": "LSPI_チャンピオンズバトル_2", + "cv_name_key": "LSPCN_チャンピオンズバトル_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2702, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002702, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713214020, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302602, + "reward_number": 1 + } + ] + }, + { + "product_id": 96, + "leader_skin_id": 2703, + "product_name_key": "LSPPN_チャンピオンズバトル_3", + "introduction_key": "LSPI_チャンピオンズバトル_3", + "cv_name_key": "LSPCN_チャンピオンズバトル_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2703, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002703, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713314020, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302603, + "reward_number": 1 + } + ] + }, + { + "product_id": 97, + "leader_skin_id": 2704, + "product_name_key": "LSPPN_チャンピオンズバトル_4", + "introduction_key": "LSPI_チャンピオンズバトル_4", + "cv_name_key": "LSPCN_チャンピオンズバトル_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2704, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002704, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713414020, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302604, + "reward_number": 1 + } + ] + }, + { + "product_id": 98, + "leader_skin_id": 2705, + "product_name_key": "LSPPN_チャンピオンズバトル_5", + "introduction_key": "LSPI_チャンピオンズバトル_5", + "cv_name_key": "LSPCN_チャンピオンズバトル_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2705, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002705, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713514010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302605, + "reward_number": 1 + } + ] + }, + { + "product_id": 99, + "leader_skin_id": 2706, + "product_name_key": "LSPPN_チャンピオンズバトル_6", + "introduction_key": "LSPI_チャンピオンズバトル_6", + "cv_name_key": "LSPCN_チャンピオンズバトル_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2706, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002706, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713614010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302606, + "reward_number": 1 + } + ] + }, + { + "product_id": 100, + "leader_skin_id": 2707, + "product_name_key": "LSPPN_チャンピオンズバトル_7", + "introduction_key": "LSPI_チャンピオンズバトル_7", + "cv_name_key": "LSPCN_チャンピオンズバトル_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2707, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002707, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 713714020, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302607, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 118, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 4000, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3053001, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 101, + "leader_skin_id": 2901, + "product_name_key": "LSPPN_グランブルーファンタジー2_1", + "introduction_key": "LSPI_グランブルーファンタジー2_1", + "cv_name_key": "LSPCN_グランブルーファンタジー2_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2901, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002801, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1191410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302801, + "reward_number": 1 + } + ] + }, + { + "product_id": 102, + "leader_skin_id": 2902, + "product_name_key": "LSPPN_グランブルーファンタジー2_2", + "introduction_key": "LSPI_グランブルーファンタジー2_2", + "cv_name_key": "LSPCN_グランブルーファンタジー2_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2902, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002802, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1192410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302802, + "reward_number": 1 + } + ] + }, + { + "product_id": 103, + "leader_skin_id": 2912, + "product_name_key": "LSPPN_グランブルーファンタジー2_3", + "introduction_key": "LSPI_グランブルーファンタジー2_3", + "cv_name_key": "LSPCN_グランブルーファンタジー2_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2912, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002803, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1192410210, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302803, + "reward_number": 1 + } + ] + }, + { + "product_id": 104, + "leader_skin_id": 2903, + "product_name_key": "LSPPN_グランブルーファンタジー2_4", + "introduction_key": "LSPI_グランブルーファンタジー2_4", + "cv_name_key": "LSPCN_グランブルーファンタジー2_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2903, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002804, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1193410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302804, + "reward_number": 1 + } + ] + }, + { + "product_id": 105, + "leader_skin_id": 2904, + "product_name_key": "LSPPN_グランブルーファンタジー2_5", + "introduction_key": "LSPI_グランブルーファンタジー2_5", + "cv_name_key": "LSPCN_グランブルーファンタジー2_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2904, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002805, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1194410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302805, + "reward_number": 1 + } + ] + }, + { + "product_id": 106, + "leader_skin_id": 2905, + "product_name_key": "LSPPN_グランブルーファンタジー2_6", + "introduction_key": "LSPI_グランブルーファンタジー2_6", + "cv_name_key": "LSPCN_グランブルーファンタジー2_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2905, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002806, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1195410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302806, + "reward_number": 1 + } + ] + }, + { + "product_id": 107, + "leader_skin_id": 2906, + "product_name_key": "LSPPN_グランブルーファンタジー2_7", + "introduction_key": "LSPI_グランブルーファンタジー2_7", + "cv_name_key": "LSPCN_グランブルーファンタジー2_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2906, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002807, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1196410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302807, + "reward_number": 1 + } + ] + }, + { + "product_id": 108, + "leader_skin_id": 2907, + "product_name_key": "LSPPN_グランブルーファンタジー2_8", + "introduction_key": "LSPI_グランブルーファンタジー2_8", + "cv_name_key": "LSPCN_グランブルーファンタジー2_8", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2907, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002808, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1197410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302808, + "reward_number": 1 + } + ] + }, + { + "product_id": 109, + "leader_skin_id": 2908, + "product_name_key": "LSPPN_グランブルーファンタジー2_9", + "introduction_key": "LSPI_グランブルーファンタジー2_9", + "cv_name_key": "LSPCN_グランブルーファンタジー2_9", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2908, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002809, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1198410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302809, + "reward_number": 1 + } + ] + }, + { + "product_id": 110, + "leader_skin_id": 2918, + "product_name_key": "LSPPN_グランブルーファンタジー2_10", + "introduction_key": "LSPI_グランブルーファンタジー2_10", + "cv_name_key": "LSPCN_グランブルーファンタジー2_10", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2918, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002810, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1198410210, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 302810, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 121, + "is_new": false, + "set_sales_status": 0, + "set_price_crystal": null, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 123, + "leader_skin_id": 2507, + "product_name_key": "LSPPN_バトルパス_1", + "introduction_key": "LSPI_バトルパス_1", + "cv_name_key": "LSPCN_バトルパス_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2507, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002601, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1147130100, + "reward_number": 1 + } + ] + }, + { + "product_id": 132, + "leader_skin_id": 2103, + "product_name_key": "LSPPN_バトルパス_2", + "introduction_key": "LSPI_バトルパス_2", + "cv_name_key": "LSPCN_バトルパス_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2103, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400002901, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1103310300, + "reward_number": 1 + } + ] + }, + { + "product_id": 148, + "leader_skin_id": 2612, + "product_name_key": "LSPPN_バトルパス_3", + "introduction_key": "LSPI_バトルパス_3", + "cv_name_key": "LSPCN_バトルパス_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2612, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000014, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900244040, + "reward_number": 1 + } + ] + }, + { + "product_id": 159, + "leader_skin_id": 2508, + "product_name_key": "LSPPN_バトルパス_4", + "introduction_key": "LSPI_バトルパス_4", + "cv_name_key": "LSPCN_バトルパス_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2508, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000019, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000018, + "reward_number": 1 + } + ] + }, + { + "product_id": 160, + "leader_skin_id": 2518, + "product_name_key": "LSPPN_バトルパス_5", + "introduction_key": "LSPI_バトルパス_5", + "cv_name_key": "LSPCN_バトルパス_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2518, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000021, + "reward_number": 1 + } + ] + }, + { + "product_id": 161, + "leader_skin_id": 2505, + "product_name_key": "LSPPN_バトルパス_6", + "introduction_key": "LSPI_バトルパス_6", + "cv_name_key": "LSPCN_バトルパス_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2505, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000025, + "reward_number": 1 + } + ] + }, + { + "product_id": 170, + "leader_skin_id": 3506, + "product_name_key": "LSPPN_バトルパス_7", + "introduction_key": "LSPI_バトルパス_7", + "cv_name_key": "LSPCN_バトルパス_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3506, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000030, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900644100, + "reward_number": 1 + } + ] + }, + { + "product_id": 175, + "leader_skin_id": 3505, + "product_name_key": "LSPPN_バトルパス_8", + "introduction_key": "LSPI_バトルパス_8", + "cv_name_key": "LSPCN_バトルパス_8", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3505, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000032, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900544110, + "reward_number": 1 + } + ] + }, + { + "product_id": 176, + "leader_skin_id": 3502, + "product_name_key": "LSPPN_バトルパス_9", + "introduction_key": "LSPI_バトルパス_9", + "cv_name_key": "LSPCN_バトルパス_9", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3502, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000036, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1242410100, + "reward_number": 1 + } + ] + }, + { + "product_id": 177, + "leader_skin_id": 3514, + "product_name_key": "LSPPN_バトルパス_10", + "introduction_key": "LSPI_バトルパス_10", + "cv_name_key": "LSPCN_バトルパス_10", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3514, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000039, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900444060, + "reward_number": 1 + } + ] + }, + { + "product_id": 178, + "leader_skin_id": 4107, + "product_name_key": "LSPPN_バトルパス_11", + "introduction_key": "LSPI_バトルパス_11", + "cv_name_key": "LSPCN_バトルパス_11", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4107, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000041, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900744060, + "reward_number": 1 + } + ] + }, + { + "product_id": 179, + "leader_skin_id": 4108, + "product_name_key": "LSPPN_バトルパス_12", + "introduction_key": "LSPI_バトルパス_12", + "cv_name_key": "LSPCN_バトルパス_12", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4108, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000046, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900844090, + "reward_number": 1 + } + ] + }, + { + "product_id": 180, + "leader_skin_id": 4103, + "product_name_key": "LSPPN_バトルパス_13", + "introduction_key": "LSPI_バトルパス_13", + "cv_name_key": "LSPCN_バトルパス_13", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4103, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000051, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900344160, + "reward_number": 1 + } + ] + }, + { + "product_id": 181, + "leader_skin_id": 4101, + "product_name_key": "LSPPN_バトルパス_14", + "introduction_key": "LSPI_バトルパス_14", + "cv_name_key": "LSPCN_バトルパス_14", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4101, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000054, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900144130, + "reward_number": 1 + } + ] + }, + { + "product_id": 182, + "leader_skin_id": 4503, + "product_name_key": "LSPPN_バトルパス_15", + "introduction_key": "LSPI_バトルパス_15", + "cv_name_key": "LSPCN_バトルパス_15", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4503, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000060, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000046, + "reward_number": 1 + } + ] + }, + { + "product_id": 183, + "leader_skin_id": 4508, + "product_name_key": "LSPPN_バトルパス_16", + "introduction_key": "LSPI_バトルパス_16", + "cv_name_key": "LSPCN_バトルパス_16", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4508, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000090, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000090, + "reward_number": 1 + } + ] + }, + { + "product_id": 184, + "leader_skin_id": 4502, + "product_name_key": "LSPPN_バトルパス_17", + "introduction_key": "LSPI_バトルパス_17", + "cv_name_key": "LSPCN_バトルパス_17", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4502, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000097, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000094, + "reward_number": 1 + } + ] + }, + { + "product_id": 185, + "leader_skin_id": 4528, + "product_name_key": "LSPPN_バトルパス_18", + "introduction_key": "LSPI_バトルパス_18", + "cv_name_key": "LSPCN_バトルパス_18", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 4528, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000098, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000095, + "reward_number": 1 + } + ] + }, + { + "product_id": 186, + "leader_skin_id": 2515, + "product_name_key": "LSPPN_バトルパス_19", + "introduction_key": "LSPI_バトルパス_19", + "cv_name_key": "LSPCN_バトルパス_19", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2515, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 200000092, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 200000092, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 123, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 3200, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3071002, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 133, + "leader_skin_id": 3601, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_1", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_1", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3601, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003907, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717121010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303201, + "reward_number": 1 + } + ] + }, + { + "product_id": 134, + "leader_skin_id": 3602, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_2", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_2", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3602, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003908, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717231010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303202, + "reward_number": 1 + } + ] + }, + { + "product_id": 135, + "leader_skin_id": 3603, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_3", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_3", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3603, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003909, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717334010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303203, + "reward_number": 1 + } + ] + }, + { + "product_id": 136, + "leader_skin_id": 3604, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_4", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_4", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3604, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003910, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717421010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303204, + "reward_number": 1 + } + ] + }, + { + "product_id": 137, + "leader_skin_id": 3605, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_5", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_5", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3605, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003911, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717514010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303205, + "reward_number": 1 + } + ] + }, + { + "product_id": 138, + "leader_skin_id": 3606, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_6", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_6", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3606, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003912, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717624010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303206, + "reward_number": 1 + } + ] + }, + { + "product_id": 139, + "leader_skin_id": 3607, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_7", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_7", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3607, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003913, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717721010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303207, + "reward_number": 1 + } + ] + }, + { + "product_id": 140, + "leader_skin_id": 3608, + "product_name_key": "LSPPN_ウマ娘_プリティーダービー_8", + "introduction_key": "LSPI_ウマ娘_プリティーダービー_8", + "cv_name_key": "LSPCN_ウマ娘_プリティーダービー_8", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3608, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003915, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717814010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303209, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 124, + "is_new": false, + "set_sales_status": 0, + "set_price_crystal": null, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [], + "products": [ + { + "product_id": 149, + "leader_skin_id": 3617, + "product_name_key": "LSPPN_ウマ娘_ゴールドシップ_1", + "introduction_key": "LSPI_ウマ娘_ゴールドシップ_1", + "cv_name_key": "LSPCN_ウマ娘_ゴールドシップ_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3617, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400003914, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 717731010, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 303208, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 125, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2800, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 200000026, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 141, + "leader_skin_id": 2602, + "product_name_key": "LSPPN_6周年スキン_1", + "introduction_key": "LSPI_6周年スキン_1", + "cv_name_key": "LSPCN_6周年スキン_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2602, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000001, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1182410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121001, + "reward_number": 1 + } + ] + }, + { + "product_id": 142, + "leader_skin_id": 2603, + "product_name_key": "LSPPN_6周年スキン_2", + "introduction_key": "LSPI_6周年スキン_2", + "cv_name_key": "LSPCN_6周年スキン_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2603, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000002, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900344080, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121002, + "reward_number": 1 + } + ] + }, + { + "product_id": 143, + "leader_skin_id": 2604, + "product_name_key": "LSPPN_6周年スキン_3", + "introduction_key": "LSPI_6周年スキン_3", + "cv_name_key": "LSPCN_6周年スキン_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2604, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000003, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900444030, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121003, + "reward_number": 1 + } + ] + }, + { + "product_id": 144, + "leader_skin_id": 2605, + "product_name_key": "LSPPN_6周年スキン_4", + "introduction_key": "LSPI_6周年スキン_4", + "cv_name_key": "LSPCN_6周年スキン_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2605, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000004, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1205410210, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121004, + "reward_number": 1 + } + ] + }, + { + "product_id": 145, + "leader_skin_id": 2606, + "product_name_key": "LSPPN_6周年スキン_5", + "introduction_key": "LSPI_6周年スキン_5", + "cv_name_key": "LSPCN_6周年スキン_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2606, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000005, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900644040, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121005, + "reward_number": 1 + } + ] + }, + { + "product_id": 146, + "leader_skin_id": 2607, + "product_name_key": "LSPPN_6周年スキン_6", + "introduction_key": "LSPI_6周年スキン_6", + "cv_name_key": "LSPCN_6周年スキン_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2607, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000006, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900744020, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121006, + "reward_number": 1 + } + ] + }, + { + "product_id": 147, + "leader_skin_id": 2608, + "product_name_key": "LSPPN_6周年スキン_7", + "introduction_key": "LSPI_6周年スキン_7", + "cv_name_key": "LSPCN_6周年スキン_7", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2608, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000007, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 1188410110, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 121007, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 127, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2400, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 3065003, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 153, + "leader_skin_id": 3701, + "product_name_key": "LSPPN_シャドウバースF_1", + "introduction_key": "LSPI_シャドウバースF_1", + "cv_name_key": "LSPCN_シャドウバースF_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3701, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400004301, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400004307, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 6, + "reward_detail_id": 717114010, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 8, + "reward_detail_id": 303401, + "reward_number": 1 + } + ] + }, + { + "product_id": 154, + "leader_skin_id": 3702, + "product_name_key": "LSPPN_シャドウバースF_2", + "introduction_key": "LSPI_シャドウバースF_2", + "cv_name_key": "LSPCN_シャドウバースF_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3702, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400004302, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400004308, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 6, + "reward_detail_id": 717234010, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 8, + "reward_detail_id": 303402, + "reward_number": 1 + } + ] + }, + { + "product_id": 155, + "leader_skin_id": 3703, + "product_name_key": "LSPPN_シャドウバースF_3", + "introduction_key": "LSPI_シャドウバースF_3", + "cv_name_key": "LSPCN_シャドウバースF_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3703, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400004303, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400004309, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 6, + "reward_detail_id": 717334020, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 8, + "reward_detail_id": 303403, + "reward_number": 1 + } + ] + }, + { + "product_id": 156, + "leader_skin_id": 3705, + "product_name_key": "LSPPN_シャドウバースF_4", + "introduction_key": "LSPI_シャドウバースF_4", + "cv_name_key": "LSPCN_シャドウバースF_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3705, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400004304, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400004310, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 6, + "reward_detail_id": 717514020, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 8, + "reward_detail_id": 303405, + "reward_number": 1 + } + ] + }, + { + "product_id": 157, + "leader_skin_id": 3706, + "product_name_key": "LSPPN_シャドウバースF_5", + "introduction_key": "LSPI_シャドウバースF_5", + "cv_name_key": "LSPCN_シャドウバースF_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3706, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400004305, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400004311, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 6, + "reward_detail_id": 717614020, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 8, + "reward_detail_id": 303406, + "reward_number": 1 + } + ] + }, + { + "product_id": 158, + "leader_skin_id": 3707, + "product_name_key": "LSPPN_シャドウバースF_6", + "introduction_key": "LSPI_シャドウバースF_6", + "cv_name_key": "LSPCN_シャドウバースF_6", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3707, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 400004306, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 7, + "reward_detail_id": 400004312, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 6, + "reward_detail_id": 717732010, + "reward_number": 1 + }, + { + "order_index": 4, + "reward_type": 8, + "reward_detail_id": 303407, + "reward_number": 1 + } + ] + } + ] + }, + { + "series_id": 129, + "is_new": false, + "set_sales_status": 1, + "set_price_crystal": 2000, + "set_price_rupy": null, + "set_price_ticket": null, + "set_price_ticket_id": null, + "set_completion_rewards": [ + { + "order_index": 0, + "reward_type": 6, + "reward_detail_id": 200000037, + "reward_number": 1 + } + ], + "products": [ + { + "product_id": 165, + "leader_skin_id": 2501, + "product_name_key": "LSPPN_7周年スキン_1", + "introduction_key": "LSPI_7周年スキン_1", + "cv_name_key": "LSPCN_7周年スキン_1", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2501, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000008, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900134030, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 123001, + "reward_number": 1 + } + ] + }, + { + "product_id": 166, + "leader_skin_id": 2523, + "product_name_key": "LSPPN_7周年スキン_2", + "introduction_key": "LSPI_7周年スキン_2", + "cv_name_key": "LSPCN_7周年スキン_2", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2523, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000009, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900334090, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 123002, + "reward_number": 1 + } + ] + }, + { + "product_id": 167, + "leader_skin_id": 2513, + "product_name_key": "LSPPN_7周年スキン_3", + "introduction_key": "LSPI_7周年スキン_3", + "cv_name_key": "LSPCN_7周年スキン_3", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 2513, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000010, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 900344090, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 123003, + "reward_number": 1 + } + ] + }, + { + "product_id": 168, + "leader_skin_id": 3504, + "product_name_key": "LSPPN_7周年スキン_4", + "introduction_key": "LSPI_7周年スキン_4", + "cv_name_key": "LSPCN_7周年スキン_4", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3504, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000011, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 820444060, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 123004, + "reward_number": 1 + } + ] + }, + { + "product_id": 169, + "leader_skin_id": 3516, + "product_name_key": "LSPPN_7周年スキン_5", + "introduction_key": "LSPI_7周年スキン_5", + "cv_name_key": "LSPCN_7周年スキン_5", + "single_price_crystal": 500, + "single_price_rupy": null, + "single_price_ticket": null, + "ticket_number": null, + "ticket_item_id": null, + "rewards": [ + { + "order_index": 0, + "reward_type": 10, + "reward_detail_id": 3516, + "reward_number": 1 + }, + { + "order_index": 1, + "reward_type": 7, + "reward_detail_id": 210000012, + "reward_number": 1 + }, + { + "order_index": 2, + "reward_type": 6, + "reward_detail_id": 820644040, + "reward_number": 1 + }, + { + "order_index": 3, + "reward_type": 8, + "reward_detail_id": 123005, + "reward_number": 1 + } + ] + } + ] + } +] diff --git a/SVSim.Bootstrap/Importers/LeaderSkinShopImporter.cs b/SVSim.Bootstrap/Importers/LeaderSkinShopImporter.cs new file mode 100644 index 0000000..5d0a880 --- /dev/null +++ b/SVSim.Bootstrap/Importers/LeaderSkinShopImporter.cs @@ -0,0 +1,115 @@ +using Microsoft.EntityFrameworkCore; +using SVSim.Bootstrap.Models.Seed; +using SVSim.Database; +using SVSim.Database.Models; + +namespace SVSim.Bootstrap.Importers; + +/// +/// Idempotent upsert of the leader-skin-shop catalog from seeds/leader-skin-shop.json. +/// Mirror of . Source is the wire +/// /leader_skin/products response, extracted via +/// data_dumps/extract/extract-leader-skin-shop.py. Rows missing from the seed are LEFT INTACT. +/// +public class LeaderSkinShopImporter +{ + public async Task ImportAsync(SVSimDbContext context, string seedDir) + { + string path = Path.Combine(seedDir, "leader-skin-shop.json"); + var seed = SeedLoader.LoadList(path); + if (seed.Count == 0) + { + Console.WriteLine("[LeaderSkinShopImporter] No seed rows; skipping."); + return 0; + } + + var existingSeries = await context.LeaderSkinShopSeries + .Include(s => s.SetCompletionRewards) + .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 LeaderSkinShopSeriesEntry { Id = s.SeriesId }; + context.LeaderSkinShopSeries.Add(series); + existingSeries[s.SeriesId] = series; + createdSeries++; + } + else updatedSeries++; + + series.IsNew = s.IsNew; + series.IsEnabled = true; + series.SetSalesStatus = s.SetSalesStatus; + series.SetPriceCrystal = s.SetPriceCrystal; + series.SetPriceRupy = s.SetPriceRupy; + series.SetPriceTicket = s.SetPriceTicket; + series.SetPriceTicketId = s.SetPriceTicketId; + // SetCompletionRewardStatus stays at the catalog default 0 — per-viewer claim state + // is computed at request time from ViewerLeaderSkinSetClaim, not from this column. + series.SetCompletionRewardStatus = 0; + + // Replace owned collections wholesale on rerun. + series.SetCompletionRewards.Clear(); + foreach (var r in s.SetCompletionRewards.OrderBy(r => r.OrderIndex)) + { + series.SetCompletionRewards.Add(new LeaderSkinShopSeriesRewardEntry + { + OrderIndex = r.OrderIndex, + RewardType = r.RewardType, + RewardDetailId = r.RewardDetailId, + RewardNumber = r.RewardNumber, + }); + } + + 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 LeaderSkinShopProductEntry { Id = p.ProductId }; + series.Products.Add(product); + createdProducts++; + } + else updatedProducts++; + + product.SeriesId = s.SeriesId; + product.LeaderSkinId = p.LeaderSkinId; + product.ProductNameKey = p.ProductNameKey; + product.IntroductionKey = p.IntroductionKey; + product.CvNameKey = p.CvNameKey; + product.SinglePriceCrystal = p.SinglePriceCrystal; + product.SinglePriceRupy = p.SinglePriceRupy; + product.SinglePriceTicket = p.SinglePriceTicket; + product.TicketNumber = p.TicketNumber; + product.TicketItemId = p.TicketItemId; + product.IsEnabled = true; + + product.Rewards.Clear(); + foreach (var r in p.Rewards.OrderBy(r => r.OrderIndex)) + { + product.Rewards.Add(new LeaderSkinShopProductRewardEntry + { + OrderIndex = r.OrderIndex, + RewardType = r.RewardType, + RewardDetailId = r.RewardDetailId, + RewardNumber = r.RewardNumber, + }); + } + } + } + + await context.SaveChangesAsync(); + Console.WriteLine( + $"[LeaderSkinShopImporter] series +{createdSeries}/~{updatedSeries}, " + + $"products +{createdProducts}/~{updatedProducts}"); + return createdSeries + updatedSeries; + } +} diff --git a/SVSim.Bootstrap/Models/Seed/LeaderSkinShopSeed.cs b/SVSim.Bootstrap/Models/Seed/LeaderSkinShopSeed.cs new file mode 100644 index 0000000..83c7917 --- /dev/null +++ b/SVSim.Bootstrap/Models/Seed/LeaderSkinShopSeed.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +namespace SVSim.Bootstrap.Models.Seed; + +public sealed class LeaderSkinShopSeriesSeed +{ + [JsonPropertyName("series_id")] public int SeriesId { get; set; } + [JsonPropertyName("is_new")] public bool IsNew { get; set; } + [JsonPropertyName("set_sales_status")] public int SetSalesStatus { get; set; } + [JsonPropertyName("set_price_crystal")] public int? SetPriceCrystal { get; set; } + [JsonPropertyName("set_price_rupy")] public int? SetPriceRupy { get; set; } + [JsonPropertyName("set_price_ticket")] public int? SetPriceTicket { get; set; } + [JsonPropertyName("set_price_ticket_id")] public long? SetPriceTicketId { get; set; } + [JsonPropertyName("set_completion_rewards")] public List SetCompletionRewards { get; set; } = new(); + [JsonPropertyName("products")] public List Products { get; set; } = new(); +} + +public sealed class LeaderSkinShopProductSeed +{ + [JsonPropertyName("product_id")] public int ProductId { get; set; } + [JsonPropertyName("leader_skin_id")] public int LeaderSkinId { get; set; } + [JsonPropertyName("product_name_key")] public string ProductNameKey { get; set; } = ""; + [JsonPropertyName("introduction_key")] public string IntroductionKey { get; set; } = ""; + [JsonPropertyName("cv_name_key")] public string CvNameKey { get; set; } = ""; + [JsonPropertyName("single_price_crystal")] public int? SinglePriceCrystal { get; set; } + [JsonPropertyName("single_price_rupy")] public int? SinglePriceRupy { get; set; } + [JsonPropertyName("single_price_ticket")] public int? SinglePriceTicket { get; set; } + [JsonPropertyName("ticket_number")] public int? TicketNumber { get; set; } + [JsonPropertyName("ticket_item_id")] public long? TicketItemId { get; set; } + [JsonPropertyName("rewards")] public List Rewards { get; set; } = new(); +} + +public sealed class LeaderSkinShopRewardSeed +{ + [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 a55450e..e2d1290 100644 --- a/SVSim.Bootstrap/Program.cs +++ b/SVSim.Bootstrap/Program.cs @@ -100,6 +100,7 @@ public static class Program await new ItemImporter().ImportAsync(context, opts.SeedDir); await new SleeveShopImporter().ImportAsync(context, opts.SeedDir); await new ItemPurchaseImporter().ImportAsync(context, opts.SeedDir); + await new LeaderSkinShopImporter().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/20260528024430_AddLeaderSkinShop.Designer.cs b/SVSim.Database/Migrations/20260528024430_AddLeaderSkinShop.Designer.cs new file mode 100644 index 0000000..23ca4b4 --- /dev/null +++ b/SVSim.Database/Migrations/20260528024430_AddLeaderSkinShop.Designer.cs @@ -0,0 +1,3627 @@ +// +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("20260528024430_AddLeaderSkinShop")] + partial class AddLeaderSkinShop + { + /// + 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.ItemPurchaseCatalogEntry", 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("IsMonthlyReset") + .HasColumnType("boolean"); + + b.Property("PurchaseItemId") + .HasColumnType("bigint"); + + b.Property("PurchaseItemNum") + .HasColumnType("integer"); + + b.Property("PurchaseItemType") + .HasColumnType("integer"); + + b.Property("PurchaseLimit") + .HasColumnType("integer"); + + b.Property("PurchaseName") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequireItemId") + .HasColumnType("bigint"); + + b.Property("RequireItemNum") + .HasColumnType("integer"); + + b.Property("RequireItemType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("ItemPurchaseCatalog"); + }); + + 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.LeaderSkinShopProductEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CvNameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IntroductionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LeaderSkinId") + .HasColumnType("integer"); + + b.Property("ProductNameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.Property("SinglePriceCrystal") + .HasColumnType("integer"); + + b.Property("SinglePriceRupy") + .HasColumnType("integer"); + + b.Property("SinglePriceTicket") + .HasColumnType("integer"); + + b.Property("TicketItemId") + .HasColumnType("bigint"); + + b.Property("TicketNumber") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("LeaderSkinShopProducts"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopSeriesEntry", 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.Property("SetCompletionRewardStatus") + .HasColumnType("integer"); + + b.Property("SetPriceCrystal") + .HasColumnType("integer"); + + b.Property("SetPriceRupy") + .HasColumnType("integer"); + + b.Property("SetPriceTicket") + .HasColumnType("integer"); + + b.Property("SetPriceTicketId") + .HasColumnType("bigint"); + + b.Property("SetSalesStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("LeaderSkinShopSeries"); + }); + + 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.ViewerLeaderSkinSetClaim", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ViewerId", "SeriesId"); + + b.HasIndex("ViewerId"); + + b.ToTable("ViewerLeaderSkinSetClaims"); + }); + + 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.LeaderSkinShopProductEntry", b => + { + b.HasOne("SVSim.Database.Models.LeaderSkinShopSeriesEntry", "Series") + .WithMany("Products") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("SVSim.Database.Models.LeaderSkinShopProductRewardEntry", "Rewards", b1 => + { + b1.Property("LeaderSkinShopProductEntryId") + .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("LeaderSkinShopProductEntryId", "Id"); + + b1.ToTable("LeaderSkinShopProductRewardEntry"); + + b1.WithOwner() + .HasForeignKey("LeaderSkinShopProductEntryId"); + }); + + b.Navigation("Rewards"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopSeriesEntry", b => + { + b.OwnsMany("SVSim.Database.Models.LeaderSkinShopSeriesRewardEntry", "SetCompletionRewards", b1 => + { + b1.Property("LeaderSkinShopSeriesEntryId") + .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("LeaderSkinShopSeriesEntryId", "Id"); + + b1.ToTable("LeaderSkinShopSeriesRewardEntry"); + + b1.WithOwner() + .HasForeignKey("LeaderSkinShopSeriesEntryId"); + }); + + b.Navigation("SetCompletionRewards"); + }); + + 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.LeaderSkinShopSeriesEntry", b => + { + b.Navigation("Products"); + }); + + 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/20260528024430_AddLeaderSkinShop.cs b/SVSim.Database/Migrations/20260528024430_AddLeaderSkinShop.cs new file mode 100644 index 0000000..35ad72f --- /dev/null +++ b/SVSim.Database/Migrations/20260528024430_AddLeaderSkinShop.cs @@ -0,0 +1,155 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace SVSim.Database.Migrations +{ + /// + public partial class AddLeaderSkinShop : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LeaderSkinShopSeries", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + IsNew = table.Column(type: "boolean", nullable: false), + IsEnabled = table.Column(type: "boolean", nullable: false), + SetSalesStatus = table.Column(type: "integer", nullable: false), + SetPriceCrystal = table.Column(type: "integer", nullable: true), + SetPriceRupy = table.Column(type: "integer", nullable: true), + SetPriceTicket = table.Column(type: "integer", nullable: true), + SetPriceTicketId = table.Column(type: "bigint", nullable: true), + SetCompletionRewardStatus = table.Column(type: "integer", 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_LeaderSkinShopSeries", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ViewerLeaderSkinSetClaims", + columns: table => new + { + ViewerId = table.Column(type: "bigint", nullable: false), + SeriesId = table.Column(type: "integer", nullable: false), + ClaimedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ViewerLeaderSkinSetClaims", x => new { x.ViewerId, x.SeriesId }); + }); + + migrationBuilder.CreateTable( + name: "LeaderSkinShopProducts", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false), + SeriesId = table.Column(type: "integer", nullable: false), + LeaderSkinId = table.Column(type: "integer", nullable: false), + ProductNameKey = table.Column(type: "text", nullable: false), + IntroductionKey = table.Column(type: "text", nullable: false), + CvNameKey = table.Column(type: "text", nullable: false), + SinglePriceCrystal = table.Column(type: "integer", nullable: true), + SinglePriceRupy = table.Column(type: "integer", nullable: true), + SinglePriceTicket = table.Column(type: "integer", nullable: true), + TicketNumber = table.Column(type: "integer", nullable: true), + TicketItemId = table.Column(type: "bigint", 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_LeaderSkinShopProducts", x => x.Id); + table.ForeignKey( + name: "FK_LeaderSkinShopProducts_LeaderSkinShopSeries_SeriesId", + column: x => x.SeriesId, + principalTable: "LeaderSkinShopSeries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LeaderSkinShopSeriesRewardEntry", + columns: table => new + { + LeaderSkinShopSeriesEntryId = 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_LeaderSkinShopSeriesRewardEntry", x => new { x.LeaderSkinShopSeriesEntryId, x.Id }); + table.ForeignKey( + name: "FK_LeaderSkinShopSeriesRewardEntry_LeaderSkinShopSeries_Leader~", + column: x => x.LeaderSkinShopSeriesEntryId, + principalTable: "LeaderSkinShopSeries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LeaderSkinShopProductRewardEntry", + columns: table => new + { + LeaderSkinShopProductEntryId = 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_LeaderSkinShopProductRewardEntry", x => new { x.LeaderSkinShopProductEntryId, x.Id }); + table.ForeignKey( + name: "FK_LeaderSkinShopProductRewardEntry_LeaderSkinShopProducts_Lea~", + column: x => x.LeaderSkinShopProductEntryId, + principalTable: "LeaderSkinShopProducts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_LeaderSkinShopProducts_SeriesId", + table: "LeaderSkinShopProducts", + column: "SeriesId"); + + migrationBuilder.CreateIndex( + name: "IX_ViewerLeaderSkinSetClaims_ViewerId", + table: "ViewerLeaderSkinSetClaims", + column: "ViewerId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LeaderSkinShopProductRewardEntry"); + + migrationBuilder.DropTable( + name: "LeaderSkinShopSeriesRewardEntry"); + + migrationBuilder.DropTable( + name: "ViewerLeaderSkinSetClaims"); + + migrationBuilder.DropTable( + name: "LeaderSkinShopProducts"); + + migrationBuilder.DropTable( + name: "LeaderSkinShopSeries"); + } + } +} diff --git a/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs b/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs index 172db9f..558cfdb 100644 --- a/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs +++ b/SVSim.Database/Migrations/SVSimDbContextModelSnapshot.cs @@ -1182,6 +1182,100 @@ namespace SVSim.Database.Migrations b.ToTable("LeaderSkins"); }); + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopProductEntry", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("CvNameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone"); + + b.Property("IntroductionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LeaderSkinId") + .HasColumnType("integer"); + + b.Property("ProductNameKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.Property("SinglePriceCrystal") + .HasColumnType("integer"); + + b.Property("SinglePriceRupy") + .HasColumnType("integer"); + + b.Property("SinglePriceTicket") + .HasColumnType("integer"); + + b.Property("TicketItemId") + .HasColumnType("bigint"); + + b.Property("TicketNumber") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("LeaderSkinShopProducts"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopSeriesEntry", 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.Property("SetCompletionRewardStatus") + .HasColumnType("integer"); + + b.Property("SetPriceCrystal") + .HasColumnType("integer"); + + b.Property("SetPriceRupy") + .HasColumnType("integer"); + + b.Property("SetPriceTicket") + .HasColumnType("integer"); + + b.Property("SetPriceTicketId") + .HasColumnType("bigint"); + + b.Property("SetSalesStatus") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("LeaderSkinShopSeries"); + }); + modelBuilder.Entity("SVSim.Database.Models.LoadingExclusionCardEntry", b => { b.Property("Id") @@ -2300,6 +2394,24 @@ namespace SVSim.Database.Migrations b.ToTable("ViewerEventCounters"); }); + modelBuilder.Entity("SVSim.Database.Models.ViewerLeaderSkinSetClaim", b => + { + b.Property("ViewerId") + .HasColumnType("bigint"); + + b.Property("SeriesId") + .HasColumnType("integer"); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ViewerId", "SeriesId"); + + b.HasIndex("ViewerId"); + + b.ToTable("ViewerLeaderSkinSetClaims"); + }); + modelBuilder.Entity("SVSim.Database.Models.ViewerMission", b => { b.Property("Id") @@ -2714,6 +2826,86 @@ namespace SVSim.Database.Migrations b.Navigation("Class"); }); + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopProductEntry", b => + { + b.HasOne("SVSim.Database.Models.LeaderSkinShopSeriesEntry", "Series") + .WithMany("Products") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("SVSim.Database.Models.LeaderSkinShopProductRewardEntry", "Rewards", b1 => + { + b1.Property("LeaderSkinShopProductEntryId") + .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("LeaderSkinShopProductEntryId", "Id"); + + b1.ToTable("LeaderSkinShopProductRewardEntry"); + + b1.WithOwner() + .HasForeignKey("LeaderSkinShopProductEntryId"); + }); + + b.Navigation("Rewards"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopSeriesEntry", b => + { + b.OwnsMany("SVSim.Database.Models.LeaderSkinShopSeriesRewardEntry", "SetCompletionRewards", b1 => + { + b1.Property("LeaderSkinShopSeriesEntryId") + .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("LeaderSkinShopSeriesEntryId", "Id"); + + b1.ToTable("LeaderSkinShopSeriesRewardEntry"); + + b1.WithOwner() + .HasForeignKey("LeaderSkinShopSeriesEntryId"); + }); + + b.Navigation("SetCompletionRewards"); + }); + modelBuilder.Entity("SVSim.Database.Models.PackConfigEntry", b => { b.OwnsMany("SVSim.Database.Models.PackBannerEntry", "Banners", b1 => @@ -3396,6 +3588,11 @@ namespace SVSim.Database.Migrations b.Navigation("LeaderSkins"); }); + modelBuilder.Entity("SVSim.Database.Models.LeaderSkinShopSeriesEntry", b => + { + b.Navigation("Products"); + }); + modelBuilder.Entity("SVSim.Database.Models.PuzzleGroupEntry", b => { b.Navigation("Puzzles"); diff --git a/SVSim.Database/Models/LeaderSkinShopProductEntry.cs b/SVSim.Database/Models/LeaderSkinShopProductEntry.cs new file mode 100644 index 0000000..53717f7 --- /dev/null +++ b/SVSim.Database/Models/LeaderSkinShopProductEntry.cs @@ -0,0 +1,36 @@ +using SVSim.Database.Common; + +namespace SVSim.Database.Models; + +/// +/// One purchasable leader-skin product. PK = wire product_id (small ints in captures — e.g. 31, +/// 165, 166). FK . points at the +/// the buyer ends up owning. +/// +public class LeaderSkinShopProductEntry : BaseEntity +{ + public int SeriesId { get; set; } + public int LeaderSkinId { get; set; } + + /// SystemText keys — resolved client-side via Data.Master.GetLeaderSkinProductText. + public string ProductNameKey { get; set; } = string.Empty; + public string IntroductionKey { get; set; } = string.Empty; + public string CvNameKey { get; set; } = string.Empty; + + /// + /// Per-product price for solo buy. Captures consistently show crystal/rupy parity for + /// regular skins (500c / 500r single, 400 unit-price when bought as set). Nullable so + /// promotions can offer one currency without the other. + /// + public int? SinglePriceCrystal { get; set; } + public int? SinglePriceRupy { get; set; } + public int? SinglePriceTicket { get; set; } + public int? TicketNumber { get; set; } + public long? TicketItemId { get; set; } + + public bool IsEnabled { get; set; } + + public List Rewards { get; set; } = new(); + + public LeaderSkinShopSeriesEntry? Series { get; set; } +} diff --git a/SVSim.Database/Models/LeaderSkinShopProductRewardEntry.cs b/SVSim.Database/Models/LeaderSkinShopProductRewardEntry.cs new file mode 100644 index 0000000..d5886fd --- /dev/null +++ b/SVSim.Database/Models/LeaderSkinShopProductRewardEntry.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; + +namespace SVSim.Database.Models; + +/// +/// One per-buy reward attached to a leader-skin product. Owned by +/// . Captures show each skin product bundles 3 rewards: +/// the skin itself (type=10), the matching emblem (type=7), and the matching sleeve (type=6). +/// +[Owned] +public class LeaderSkinShopProductRewardEntry +{ + 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/LeaderSkinShopSeriesEntry.cs b/SVSim.Database/Models/LeaderSkinShopSeriesEntry.cs new file mode 100644 index 0000000..eaa65e8 --- /dev/null +++ b/SVSim.Database/Models/LeaderSkinShopSeriesEntry.cs @@ -0,0 +1,33 @@ +using SVSim.Database.Common; + +namespace SVSim.Database.Models; + +/// +/// One leader-skin-shop series (a themed collection — e.g. "7th Anniversary Skins"). +/// PK = wire series_id. controls whether the per-series +/// "buy whole set" UI is offered: 0=none (single-skin purchases only), non-zero=set sale active. +/// When set-active, the set-price + set-completion-reward fields are populated. +/// +public class LeaderSkinShopSeriesEntry : BaseEntity +{ + public bool IsNew { get; set; } + public bool IsEnabled { get; set; } + + /// SkinSeriesPurchaseInfo.eSetSalesStatus — 0=None. + public int SetSalesStatus { get; set; } + + public int? SetPriceCrystal { get; set; } + public int? SetPriceRupy { get; set; } + public int? SetPriceTicket { get; set; } + public long? SetPriceTicketId { get; set; } + + /// + /// SkinSeriesPurchaseInfo.RewardStatus — 0=none. The per-VIEWER claim state is computed + /// at request time from ; this column is the catalog + /// default surfaced when no viewer is in context (or when set_sales_status==0). + /// + public int SetCompletionRewardStatus { get; set; } + + public List Products { get; set; } = new(); + public List SetCompletionRewards { get; set; } = new(); +} diff --git a/SVSim.Database/Models/LeaderSkinShopSeriesRewardEntry.cs b/SVSim.Database/Models/LeaderSkinShopSeriesRewardEntry.cs new file mode 100644 index 0000000..7971f9f --- /dev/null +++ b/SVSim.Database/Models/LeaderSkinShopSeriesRewardEntry.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; + +namespace SVSim.Database.Models; + +/// +/// One set-completion bonus item attached to a leader-skin series. Owned by +/// . Granted by /leader_skin/buy_set_item once the +/// viewer owns every skin in the series. Wire shape: entries inside +/// rewards.items[] on the per-series block of /leader_skin/products. +/// +[Owned] +public class LeaderSkinShopSeriesRewardEntry +{ + public int OrderIndex { get; set; } + public int RewardType { get; set; } + public long RewardDetailId { get; set; } + public int RewardNumber { get; set; } +} diff --git a/SVSim.Database/Models/ViewerLeaderSkinSetClaim.cs b/SVSim.Database/Models/ViewerLeaderSkinSetClaim.cs new file mode 100644 index 0000000..eb2ca9c --- /dev/null +++ b/SVSim.Database/Models/ViewerLeaderSkinSetClaim.cs @@ -0,0 +1,14 @@ +namespace SVSim.Database.Models; + +/// +/// One row per (viewer, leader-skin series) marking that the viewer has claimed the +/// series-completion bonus via /leader_skin/buy_set_item. Composite PK (ViewerId, SeriesId). +/// Standalone table (not a Viewer owned collection) to avoid the cartesian-explode pitfall +/// when loading the viewer graph — claim state is checked per-series, not per-viewer-load. +/// +public class ViewerLeaderSkinSetClaim +{ + public long ViewerId { get; set; } + public int SeriesId { get; set; } + public DateTime ClaimedAt { get; set; } +} diff --git a/SVSim.Database/SVSimDbContext.cs b/SVSim.Database/SVSimDbContext.cs index dcc98d3..43efff2 100644 --- a/SVSim.Database/SVSimDbContext.cs +++ b/SVSim.Database/SVSimDbContext.cs @@ -73,6 +73,9 @@ public class SVSimDbContext : DbContext public DbSet SleeveShopSeries => Set(); public DbSet SleeveShopProducts => Set(); public DbSet ItemPurchaseCatalog => Set(); + public DbSet LeaderSkinShopSeries => Set(); + public DbSet LeaderSkinShopProducts => Set(); + public DbSet ViewerLeaderSkinSetClaims => Set(); public DbSet MaintenanceCards => Set(); public DbSet FeatureMaintenances => Set(); public DbSet PreReleaseInfos => Set(); @@ -191,6 +194,21 @@ public class SVSimDbContext : DbContext .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasIndex(p => p.SeriesId); + modelBuilder.Entity().OwnsMany(s => s.SetCompletionRewards); + 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(c => new { c.ViewerId, c.SeriesId }); + b.HasIndex(c => c.ViewerId); + }); + modelBuilder.Entity(b => { b.HasKey(r => new { r.CardId, r.Type, r.CosmeticId }); diff --git a/SVSim.EmulatedEntrypoint/Controllers/LeaderSkinController.cs b/SVSim.EmulatedEntrypoint/Controllers/LeaderSkinController.cs index 4b0771a..927db7f 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/LeaderSkinController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/LeaderSkinController.cs @@ -1,24 +1,40 @@ 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.LeaderSkin; using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.LeaderSkin; namespace SVSim.EmulatedEntrypoint.Controllers; /// -/// /leader_skin/* — per-class "active leader skin" preference. The per-CLASS setting is the -/// fallback used when a deck has leader_skin_id == 0; per-deck overrides go through -/// /deck/update_leader_skin instead. +/// /leader_skin/* — the leader-skin shop family. +/// +/// /set: per-class equipped-skin preference (the fallback when a deck has +/// leader_skin_id == 0). Per-deck overrides go through /deck/update_leader_skin. +/// /products: shop catalog (dict-keyed by series_id). +/// /buy: single-skin purchase. Currency dispatch crystal/rupy/ticket(501). +/// /buy_set: whole-series purchase at set discount. +/// /buy_set_item: claim series-completion bonus (idempotent via +/// ). +/// /ids: flat list of owned skin ids for badge refresh. +/// /// [Route("leader_skin")] public class LeaderSkinController : SVSimController { private readonly SVSimDbContext _db; + private readonly RewardGrantService _rewards; + private readonly TimeProvider _time; - public LeaderSkinController(SVSimDbContext db) + public LeaderSkinController(SVSimDbContext db, RewardGrantService rewards, TimeProvider time) { _db = db; + _rewards = rewards; + _time = time; } [HttpPost("set")] @@ -28,8 +44,6 @@ public class LeaderSkinController : SVSimController if (request.IsRandomLeaderSkin) { - // Random-skin mode needs a per-viewer per-class shuffle pool, which we don't - // persist yet (ViewerClassData has no list field for it). Punt for now. return StatusCode(StatusCodes.Status501NotImplemented, new { error = "random_leader_skin_not_implemented" }); } @@ -44,7 +58,6 @@ public class LeaderSkinController : SVSimController var classData = viewer.Classes.FirstOrDefault(c => c.Class.Id == request.ClassId); if (classData is null) return BadRequest(new { error = "unknown_class" }); - // Skin must (a) exist in the catalog, (b) match the target class, (c) be owned by the viewer. var skin = await _db.LeaderSkins.FindAsync(request.LeaderSkinId); if (skin is null) return BadRequest(new { error = "unknown_skin" }); if (skin.ClassId != request.ClassId) return BadRequest(new { error = "skin_class_mismatch" }); @@ -61,4 +74,336 @@ public class LeaderSkinController : SVSimController LeaderSkinIdList = new(), }; } + + [HttpPost("ids")] + public async Task> Ids() + { + if (!TryGetViewerId(out long viewerId)) return Unauthorized(); + + var ids = await _db.Viewers + .Where(v => v.Id == viewerId) + .SelectMany(v => v.LeaderSkins.Select(s => s.Id)) + .OrderBy(id => id) + .ToListAsync(); + + return new LeaderSkinIdsResponse { UserLeaderSkinIds = ids }; + } + + [HttpPost("products")] + public async Task>> Products() + { + if (!TryGetViewerId(out long viewerId)) return Unauthorized(); + + var ownedSkinIds = (await _db.Viewers + .Where(v => v.Id == viewerId) + .SelectMany(v => v.LeaderSkins.Select(s => s.Id)) + .ToListAsync()).ToHashSet(); + + var claimedSeries = (await _db.ViewerLeaderSkinSetClaims + .Where(c => c.ViewerId == viewerId) + .Select(c => c.SeriesId) + .ToListAsync()).ToHashSet(); + + var series = await _db.LeaderSkinShopSeries + .Where(s => s.IsEnabled) + .Include(s => s.SetCompletionRewards) + .Include(s => s.Products.Where(p => p.IsEnabled)).ThenInclude(p => p.Rewards) + .OrderBy(s => s.Id) + .ToListAsync(); + + var result = new Dictionary(); + foreach (var s in series) + { + var products = s.Products.OrderBy(p => p.Id).Select(p => ToProductDto(p, ownedSkinIds)).ToList(); + bool seriesCompleted = products.Count > 0 && products.All(p => p.IsPurchased); + int rewardStatus = ComputeRewardStatus(s, seriesCompleted, claimedSeries.Contains(s.Id)); + + result[s.Id.ToString()] = new SkinSeriesDto + { + SeriesId = s.Id, + IsCompleted = seriesCompleted, + IsNew = s.IsNew, + SetSalesStatus = s.SetSalesStatus, + Rewards = new SkinSeriesRewardsDto + { + Status = rewardStatus, + Items = s.SetCompletionRewards.OrderBy(r => r.OrderIndex).Select(r => new SkinSeriesRewardItemDto + { + RewardType = r.RewardType, + RewardDetailId = r.RewardDetailId, + RewardNumber = r.RewardNumber, + }).ToList(), + }, + SetPrices = new SkinSeriesSetPricesDto + { + SetPriceCrystal = s.SetPriceCrystal, + SetPriceRupy = s.SetPriceRupy, + SetPriceTicket = s.SetPriceTicket, + TicketId = s.SetPriceTicketId, + }, + Products = products, + }; + } + + return result; + } + + [HttpPost("buy")] + public async Task> Buy(LeaderSkinBuyRequest 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.LeaderSkinShopProducts + .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" }); + + var viewer = await LoadViewerGraphAsync(viewerId); + + // Already-purchased = viewer owns the leader_skin this product grants. + if (viewer.LeaderSkins.Any(s => s.Id == product.LeaderSkinId)) + return BadRequest(new { error = "already_purchased" }); + + var rewardList = new List(); + var debit = DebitProductPrice(viewer, product, request.SalesType); + if (debit.Error is not null) return BadRequest(new { error = debit.Error }); + if (debit.PostState is not null) rewardList.Add(debit.PostState); + + await ApplyRewardsAsync(viewer, product.Rewards, rewardList); + + await _db.SaveChangesAsync(); + return new LeaderSkinBuyResponse { RewardList = rewardList }; + } + + [HttpPost("buy_set")] + public async Task> BuySet(LeaderSkinBuySetRequest 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 series = await _db.LeaderSkinShopSeries + .Include(s => s.Products.Where(p => p.IsEnabled)).ThenInclude(p => p.Rewards) + .FirstOrDefaultAsync(s => s.Id == request.SeriesId); + if (series is null) return NotFound(new { error = "unknown_series" }); + if (!series.IsEnabled || series.SetSalesStatus == 0) + return BadRequest(new { error = "set_sale_not_active" }); + + var viewer = await LoadViewerGraphAsync(viewerId); + + var rewardList = new List(); + var debit = DebitSetPrice(viewer, series, request.SalesType); + if (debit.Error is not null) return BadRequest(new { error = debit.Error }); + if (debit.PostState is not null) rewardList.Add(debit.PostState); + + // Grant every product's rewards; RewardGrantService is idempotent on already-owned + // cosmetics, so partial-set buyers don't double-add. + foreach (var p in series.Products.OrderBy(p => p.Id)) + { + await ApplyRewardsAsync(viewer, p.Rewards, rewardList); + } + + await _db.SaveChangesAsync(); + return new LeaderSkinBuyResponse { RewardList = rewardList }; + } + + [HttpPost("buy_set_item")] + public async Task> BuySetItem(LeaderSkinBuySetItemRequest request) + { + if (!TryGetViewerId(out long viewerId)) return Unauthorized(); + + var series = await _db.LeaderSkinShopSeries + .Include(s => s.SetCompletionRewards) + .Include(s => s.Products.Where(p => p.IsEnabled)) + .FirstOrDefaultAsync(s => s.Id == request.SeriesId); + if (series is null) return NotFound(new { error = "unknown_series" }); + + // Check claim hasn't been made already (idempotent — returns empty reward_list rather + // than 400 so the client doesn't error if it retries). + var existingClaim = await _db.ViewerLeaderSkinSetClaims + .FirstOrDefaultAsync(c => c.ViewerId == viewerId && c.SeriesId == series.Id); + if (existingClaim is not null) + return new LeaderSkinBuyResponse { RewardList = new() }; + + var viewer = await LoadViewerGraphAsync(viewerId); + + // Must own every skin in the series to claim the bonus. + var ownedSkinIds = viewer.LeaderSkins.Select(s => s.Id).ToHashSet(); + bool ownsAll = series.Products.Count > 0 && series.Products.All(p => ownedSkinIds.Contains(p.LeaderSkinId)); + if (!ownsAll) + return BadRequest(new { error = "series_not_completed" }); + + var rewardList = new List(); + await ApplyRewardsAsync(viewer, series.SetCompletionRewards, rewardList); + + _db.ViewerLeaderSkinSetClaims.Add(new ViewerLeaderSkinSetClaim + { + ViewerId = viewerId, + SeriesId = series.Id, + ClaimedAt = _time.GetUtcNow().UtcDateTime, + }); + + await _db.SaveChangesAsync(); + return new LeaderSkinBuyResponse { RewardList = rewardList }; + } + + /// + /// Computes the per-viewer rewards.status for a series: + /// 0=none — set_sales_status==0 (no set sale active) + /// 1=not_got — series completed by viewer but bonus unclaimed + /// 2=got — viewer claimed the bonus + /// 1 (effectively "available later") when set sale active but viewer hasn't completed it. + /// The 1/2 distinction matches the client enum (RewardStatus.not_got vs .got). + /// + private static int ComputeRewardStatus(LeaderSkinShopSeriesEntry series, bool seriesCompleted, bool claimed) + { + if (series.SetSalesStatus == 0) return 0; + if (claimed) return 2; + if (seriesCompleted) return 1; + return 1; + } + + private static SkinProductDto ToProductDto(LeaderSkinShopProductEntry p, HashSet ownedSkinIds) + { + bool isPurchased = ownedSkinIds.Contains(p.LeaderSkinId); + return new SkinProductDto + { + ProductId = p.Id, + LeaderSkinId = p.LeaderSkinId, + ProductName = p.ProductNameKey, + Introduction = p.IntroductionKey, + CvName = p.CvNameKey, + IsPurchased = isPurchased, + Sale = new SkinProductSaleDto + { + SinglePriceCrystal = p.SinglePriceCrystal, + SinglePriceRupy = p.SinglePriceRupy, + SinglePriceTicket = p.SinglePriceTicket, + TicketNumber = p.TicketNumber, + ItemId = p.TicketItemId, + }, + Rewards = p.Rewards.OrderBy(r => r.OrderIndex).Select(r => new SkinProductRewardDto + { + RewardType = r.RewardType, + RewardDetailId = r.RewardDetailId, + RewardNumber = r.RewardNumber, + IsOwned = IsRewardOwned(r, ownedSkinIds), + }).ToList(), + }; + } + + /// + /// A bundled reward shows as "owned" when the viewer already has the cosmetic. For now we + /// only flag the Skin reward (type==10) against the viewer's skin collection — the cascaded + /// emblem/sleeve typically come with the skin, so the heuristic is "skin owned → all three + /// bundle items are de-facto owned." Refine later if a capture shows independent state. + /// + private static bool IsRewardOwned(LeaderSkinShopProductRewardEntry r, HashSet ownedSkinIds) + { + // Skin reward: direct check. + if (r.RewardType == (int)UserGoodsType.Skin) + return ownedSkinIds.Contains((int)r.RewardDetailId); + // Other types: we don't have the full cosmetic-owned graph in scope here. The product's + // sibling Skin reward tells us whether the bundle was purchased; piggy-back on that by + // letting the caller pre-compute IsPurchased. Conservative default: not owned. + return false; + } + + private (RewardListEntry? PostState, string? Error) DebitProductPrice( + Viewer viewer, LeaderSkinShopProductEntry product, int salesType) + { + return salesType switch + { + 0 when product.SinglePriceCrystal == 0 && product.SinglePriceRupy == 0 => (null, null), + 0 => (null, "price_not_available_for_currency"), + 1 => product.SinglePriceCrystal is null + ? (null, "price_not_available_for_currency") + : DebitCrystal(viewer, product.SinglePriceCrystal.Value), + 2 => product.SinglePriceRupy is null + ? (null, "price_not_available_for_currency") + : DebitRupy(viewer, product.SinglePriceRupy.Value), + _ => (null, "invalid_sales_type"), + }; + } + + private (RewardListEntry? PostState, string? Error) DebitSetPrice( + Viewer viewer, LeaderSkinShopSeriesEntry series, int salesType) + { + return salesType switch + { + 0 when series.SetPriceCrystal == 0 && series.SetPriceRupy == 0 => (null, null), + 0 => (null, "price_not_available_for_currency"), + 1 => series.SetPriceCrystal is null + ? (null, "price_not_available_for_currency") + : DebitCrystal(viewer, series.SetPriceCrystal.Value), + 2 => series.SetPriceRupy is null + ? (null, "price_not_available_for_currency") + : DebitRupy(viewer, series.SetPriceRupy.Value), + _ => (null, "invalid_sales_type"), + }; + } + + private static (RewardListEntry?, string?) DebitCrystal(Viewer viewer, int amount) + { + if (viewer.Currency.Crystals < (ulong)amount) return (null, "insufficient_crystals"); + viewer.Currency.Crystals -= (ulong)amount; + return (new RewardListEntry { RewardType = 2, RewardId = 0, RewardNum = (int)viewer.Currency.Crystals }, null); + } + + private static (RewardListEntry?, string?) DebitRupy(Viewer viewer, int amount) + { + if (viewer.Currency.Rupees < (ulong)amount) return (null, "insufficient_rupees"); + viewer.Currency.Rupees -= (ulong)amount; + return (new RewardListEntry { RewardType = 9, RewardId = 0, RewardNum = (int)viewer.Currency.Rupees }, null); + } + + private async Task ApplyRewardsAsync( + Viewer viewer, IEnumerable rewards, List rewardList) where T : notnull + { + foreach (var r in rewards) + { + var (type, detailId, number) = ExtractTuple(r); + var granted = await _rewards.ApplyAsync(viewer, (UserGoodsType)type, detailId, number); + foreach (var g in granted) + { + rewardList.Add(new RewardListEntry + { + RewardType = g.RewardType, + RewardId = g.RewardId, + RewardNum = g.RewardNum, + }); + } + } + } + + private static (int Type, long Id, int Num) ExtractTuple(object reward) => reward switch + { + LeaderSkinShopProductRewardEntry p => (p.RewardType, p.RewardDetailId, p.RewardNumber), + LeaderSkinShopSeriesRewardEntry s => (s.RewardType, s.RewardDetailId, s.RewardNumber), + _ => throw new InvalidOperationException($"unexpected reward type {reward.GetType().Name}"), + }; + + private Task LoadViewerGraphAsync(long viewerId) => _db.Viewers + .Include(v => v.LeaderSkins) + .Include(v => v.Sleeves) + .Include(v => v.Emblems) + .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/LeaderSkin/LeaderSkinBuyRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuyRequest.cs new file mode 100644 index 0000000..0c91ad6 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuyRequest.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.LeaderSkin; + +/// +/// /leader_skin/buy request body. sales_type is ShopCommonUtility.SalesType: +/// 0=free, 1=crystal, 2=rupy, 3=ticket (v1: 3 returns 501 — no ticket-priced skin captured). +/// is the ticket item id when paying with a ticket, null otherwise. +/// +[MessagePackObject] +public class LeaderSkinBuyRequest : BaseRequest +{ + [JsonPropertyName("product_id")] + [Key("product_id")] + public int ProductId { get; set; } + + [JsonPropertyName("sales_type")] + [Key("sales_type")] + public int SalesType { get; set; } + + [JsonPropertyName("item_id")] + [Key("item_id")] + public long? ItemId { get; set; } +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuySetItemRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuySetItemRequest.cs new file mode 100644 index 0000000..4789c5c --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuySetItemRequest.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.LeaderSkin; + +/// +/// /leader_skin/buy_set_item — claim the series-completion bonus once every skin in the series +/// is owned. sales_type field exists on the client's param class but is never set; server +/// ignores it. +/// +[MessagePackObject] +public class LeaderSkinBuySetItemRequest : BaseRequest +{ + [JsonPropertyName("series_id")] + [Key("series_id")] + public int SeriesId { get; set; } +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuySetRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuySetRequest.cs new file mode 100644 index 0000000..6d81d66 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/LeaderSkin/LeaderSkinBuySetRequest.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests.LeaderSkin; + +/// +/// /leader_skin/buy_set — purchase every skin in a series in one call (cheaper per-skin). +/// +[MessagePackObject] +public class LeaderSkinBuySetRequest : BaseRequest +{ + [JsonPropertyName("series_id")] + [Key("series_id")] + public int SeriesId { get; set; } + + [JsonPropertyName("sales_type")] + [Key("sales_type")] + public int SalesType { get; set; } + + [JsonPropertyName("item_id")] + [Key("item_id")] + public long? ItemId { get; set; } +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinBuyResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinBuyResponse.cs new file mode 100644 index 0000000..4d46817 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinBuyResponse.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.LeaderSkin; + +/// +/// /leader_skin/buy, /leader_skin/buy_set, /leader_skin/buy_set_item all return the same shape: +/// a reward_list of standard entries (post-state totals +/// for currencies, grant counts for cosmetics). +/// +[MessagePackObject] +public class LeaderSkinBuyResponse +{ + [JsonPropertyName("reward_list")] + [Key("reward_list")] + public List RewardList { get; set; } = new(); +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinIdsResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinIdsResponse.cs new file mode 100644 index 0000000..6d06075 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinIdsResponse.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; +using MessagePack; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.LeaderSkin; + +/// +/// /leader_skin/ids — flat list of leader_skin_ids the viewer owns. Used by the client to +/// refresh badges across the skin-selection UI without re-fetching the full shop catalog. +/// +[MessagePackObject] +public class LeaderSkinIdsResponse +{ + [JsonPropertyName("user_leader_skin_ids")] + [Key("user_leader_skin_ids")] + public List UserLeaderSkinIds { get; set; } = new(); +} diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinProductsResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinProductsResponse.cs new file mode 100644 index 0000000..34f14da --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/LeaderSkin/LeaderSkinProductsResponse.cs @@ -0,0 +1,181 @@ +using System.Text.Json.Serialization; +using MessagePack; + +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.LeaderSkin; + +// /leader_skin/products wire shape: `data` IS the per-series dict (no wrapping field). +// Per SkinPurchaseInfoTask.Parse line 31: `JsonData jsonData = base.ResponseData["data"];` +// then iterates positionally. Dict-keyed-by-series_id_string mirrors the prod capture exactly. +// The controller returns Dictionary directly so `data` becomes that dict. + +[MessagePackObject] +public class SkinSeriesDto +{ + [JsonPropertyName("series_id")] + [Key("series_id")] + public int SeriesId { get; set; } + + /// True when the viewer owns every product's skin in this series. + [JsonPropertyName("is_completed")] + [Key("is_completed")] + public bool IsCompleted { get; set; } + + [JsonPropertyName("is_new")] + [Key("is_new")] + public bool IsNew { get; set; } + + /// Always emit — client unconditionally calls .ToInt() on this field. + [JsonPropertyName("set_sales_status")] + [Key("set_sales_status")] + public int SetSalesStatus { get; set; } + + [JsonPropertyName("rewards")] + [Key("rewards")] + public SkinSeriesRewardsDto Rewards { get; set; } = new(); + + /// Always emit — client reads this dict when set_sales_status != 0. + [JsonPropertyName("set_prices")] + [Key("set_prices")] + public SkinSeriesSetPricesDto SetPrices { get; set; } = new(); + + [JsonPropertyName("sales_period_info")] + [Key("sales_period_info")] + public List SalesPeriodInfo { get; set; } = new(); + + [JsonPropertyName("products")] + [Key("products")] + public List Products { get; set; } = new(); +} + +[MessagePackObject] +public class SkinSeriesRewardsDto +{ + /// SkinSeriesPurchaseInfo.RewardStatus — 0=none, 1=not_got, 2=got. + [JsonPropertyName("status")] + [Key("status")] + public int Status { get; set; } + + [JsonPropertyName("items")] + [Key("items")] + public List Items { get; set; } = new(); +} + +[MessagePackObject] +public class SkinSeriesRewardItemDto +{ + [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; } +} + +[MessagePackObject] +public class SkinSeriesSetPricesDto +{ + [JsonPropertyName("set_price_crystal")] + [Key("set_price_crystal")] + public int? SetPriceCrystal { get; set; } + + [JsonPropertyName("set_price_rupy")] + [Key("set_price_rupy")] + public int? SetPriceRupy { get; set; } + + [JsonPropertyName("set_price_ticket")] + [Key("set_price_ticket")] + public int? SetPriceTicket { get; set; } + + [JsonPropertyName("ticket_id")] + [Key("ticket_id")] + public long? TicketId { get; set; } +} + +[MessagePackObject] +public class SkinProductDto +{ + [JsonPropertyName("product_id")] + [Key("product_id")] + public int ProductId { get; set; } + + [JsonPropertyName("leader_skin_id")] + [Key("leader_skin_id")] + public int LeaderSkinId { get; set; } + + [JsonPropertyName("product_name")] + [Key("product_name")] + public string ProductName { get; set; } = string.Empty; + + [JsonPropertyName("introduction")] + [Key("introduction")] + public string Introduction { get; set; } = string.Empty; + + [JsonPropertyName("cv_name")] + [Key("cv_name")] + public string CvName { get; set; } = string.Empty; + + [JsonPropertyName("is_purchased")] + [Key("is_purchased")] + public bool IsPurchased { get; set; } + + [JsonPropertyName("sale")] + [Key("sale")] + public SkinProductSaleDto Sale { get; set; } = new(); + + [JsonPropertyName("sales_period_info")] + [Key("sales_period_info")] + public List SalesPeriodInfo { get; set; } = new(); + + [JsonPropertyName("rewards")] + [Key("rewards")] + public List Rewards { get; set; } = new(); +} + +[MessagePackObject] +public class SkinProductSaleDto +{ + [JsonPropertyName("single_price_crystal")] + [Key("single_price_crystal")] + public int? SinglePriceCrystal { get; set; } + + [JsonPropertyName("single_price_rupy")] + [Key("single_price_rupy")] + public int? SinglePriceRupy { get; set; } + + [JsonPropertyName("single_price_ticket")] + [Key("single_price_ticket")] + public int? SinglePriceTicket { get; set; } + + [JsonPropertyName("ticket_number")] + [Key("ticket_number")] + public int? TicketNumber { get; set; } + + [JsonPropertyName("item_id")] + [Key("item_id")] + public long? ItemId { get; set; } +} + +[MessagePackObject] +public class SkinProductRewardDto +{ + [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; } + + [JsonPropertyName("is_owned")] + [Key("is_owned")] + public bool IsOwned { get; set; } +} diff --git a/SVSim.UnitTests/Controllers/LeaderSkinShopControllerTests.cs b/SVSim.UnitTests/Controllers/LeaderSkinShopControllerTests.cs new file mode 100644 index 0000000..c543231 --- /dev/null +++ b/SVSim.UnitTests/Controllers/LeaderSkinShopControllerTests.cs @@ -0,0 +1,297 @@ +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; + +/// +/// Tests for the four new shop endpoints (/products, /buy, /buy_set, /buy_set_item, /ids). +/// Existing /set tests live in the older smoke-test files; this class only covers the new +/// surface added with the leader-skin-shop family. +/// +public class LeaderSkinShopControllerTests +{ + private static StringContent JsonBody(string json) => new(json, Encoding.UTF8, "application/json"); + + /// + /// Seeds one series (9001) with 2 products (skins 9101, 9102). Each product grants + /// only its skin (no emblem/sleeve cascade — keeps the test self-contained without + /// touching the cosmetic catalog). Set sale active at 800 crystals / 800 rupy. + /// Set-completion bonus is 500 rupy. + /// + private static async Task SeedShop(SVSimTestFactory f) + { + using var scope = f.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + // LeaderSkin cosmetic catalog rows (RewardGrantService.AddCosmeticIfMissing looks these up) + if (!await db.LeaderSkins.AnyAsync(s => s.Id == 9101)) + db.LeaderSkins.Add(new LeaderSkinEntry { Id = 9101, ClassId = 1 }); + if (!await db.LeaderSkins.AnyAsync(s => s.Id == 9102)) + db.LeaderSkins.Add(new LeaderSkinEntry { Id = 9102, ClassId = 1 }); + + db.LeaderSkinShopSeries.Add(new LeaderSkinShopSeriesEntry + { + Id = 9001, IsEnabled = true, IsNew = false, + SetSalesStatus = 1, SetPriceCrystal = 800, SetPriceRupy = 800, + Products = + { + new LeaderSkinShopProductEntry + { + Id = 90011, SeriesId = 9001, LeaderSkinId = 9101, + ProductNameKey = "LSPPN_test_1", IntroductionKey = "LSPI_test_1", CvNameKey = "LSPCN_test_1", + SinglePriceCrystal = 500, SinglePriceRupy = 500, IsEnabled = true, + Rewards = { new LeaderSkinShopProductRewardEntry { OrderIndex = 0, RewardType = 10, RewardDetailId = 9101, RewardNumber = 1 } }, + }, + new LeaderSkinShopProductEntry + { + Id = 90012, SeriesId = 9001, LeaderSkinId = 9102, + ProductNameKey = "LSPPN_test_2", IntroductionKey = "LSPI_test_2", CvNameKey = "LSPCN_test_2", + SinglePriceCrystal = 500, SinglePriceRupy = 500, IsEnabled = true, + Rewards = { new LeaderSkinShopProductRewardEntry { OrderIndex = 0, RewardType = 10, RewardDetailId = 9102, RewardNumber = 1 } }, + }, + }, + SetCompletionRewards = + { + new LeaderSkinShopSeriesRewardEntry { OrderIndex = 0, RewardType = 9, RewardDetailId = 0, RewardNumber = 500 }, + }, + }); + await db.SaveChangesAsync(); + } + + private static async Task SetViewerCurrency(SVSimTestFactory f, long viewerId, ulong crystals = 0, ulong rupies = 0) + { + using var scope = f.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var v = await db.Viewers.FirstAsync(x => x.Id == viewerId); + v.Currency.Crystals = crystals; + v.Currency.Rupees = rupies; + await db.SaveChangesAsync(); + } + + [Test] + public async Task Products_returns_dict_keyed_by_series_id_with_set_fields_emitted() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedShop(factory); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/leader_skin/products", + 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 root = doc.RootElement; + Assert.That(root.ValueKind, Is.EqualTo(JsonValueKind.Object), "wire shape is dict-keyed by series_id string"); + + var series = root.GetProperty("9001"); + Assert.That(series.GetProperty("series_id").GetInt32(), Is.EqualTo(9001)); + Assert.That(series.GetProperty("set_sales_status").GetInt32(), Is.EqualTo(1)); + Assert.That(series.GetProperty("set_prices").GetProperty("set_price_crystal").GetInt32(), Is.EqualTo(800)); + + var products = series.GetProperty("products"); + Assert.That(products.GetArrayLength(), Is.EqualTo(2)); + Assert.That(products[0].GetProperty("is_purchased").GetBoolean(), Is.False); + + // set_completion bonus item should be in rewards.items + var rewards = series.GetProperty("rewards"); + Assert.That(rewards.GetProperty("items").GetArrayLength(), Is.EqualTo(1)); + } + + [Test] + public async Task Buy_single_crystal_debits_and_grants_skin() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedShop(factory); + await SetViewerCurrency(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/leader_skin/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","product_id":90011,"sales_type":1,"item_id":null}""")); + + 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(2)); // crystal post-state + skin grant + + var crystal = rewardList[0]; + Assert.That(crystal.GetProperty("reward_type").GetInt32(), Is.EqualTo(2)); + Assert.That(crystal.GetProperty("reward_num").GetInt32(), Is.EqualTo(500)); + + var skin = rewardList[1]; + Assert.That(skin.GetProperty("reward_type").GetInt32(), Is.EqualTo(10)); + Assert.That(skin.GetProperty("reward_id").GetInt64(), Is.EqualTo(9101)); + + // Viewer should now own skin 9101 + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var v = await db.Viewers.Include(v => v.LeaderSkins).FirstAsync(v => v.Id == viewerId); + Assert.That(v.LeaderSkins.Any(s => s.Id == 9101), Is.True); + } + + [Test] + public async Task Buy_already_purchased_skin_rejects_with_400() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedShop(factory); + await SetViewerCurrency(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var first = await client.PostAsync("/leader_skin/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","product_id":90011,"sales_type":1,"item_id":null}""")); + Assert.That(first.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + + var second = await client.PostAsync("/leader_skin/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","product_id":90011,"sales_type":1,"item_id":null}""")); + 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 SeedShop(factory); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/leader_skin/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","product_id":90011,"sales_type":3,"item_id":900001}""")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotImplemented)); + } + + [Test] + public async Task BuySet_grants_all_skins_in_series() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedShop(factory); + await SetViewerCurrency(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/leader_skin/buy_set", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"sales_type":1,"item_id":null}""")); + + 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 + skin1 + skin2 + + // Viewer should own both skins + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var v = await db.Viewers.Include(v => v.LeaderSkins).FirstAsync(v => v.Id == viewerId); + Assert.That(v.LeaderSkins.Count(s => s.Id == 9101 || s.Id == 9102), Is.EqualTo(2)); + Assert.That(v.Currency.Crystals, Is.EqualTo(200UL)); // 1000 - 800 set price + } + + [Test] + public async Task BuySetItem_rejects_until_series_completed_then_succeeds() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedShop(factory); + await SetViewerCurrency(factory, viewerId, rupies: 5000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + // Without owning any skin: rejected + var early = await client.PostAsync("/leader_skin/buy_set_item", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001}""")); + Assert.That(early.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + + // Buy both skins via buy_set + var setBuy = await client.PostAsync("/leader_skin/buy_set", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001,"sales_type":2,"item_id":null}""")); + Assert.That(setBuy.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + + // Now claim succeeds, grants the bonus (500 rupy) + var claim = await client.PostAsync("/leader_skin/buy_set_item", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001}""")); + var body = await claim.Content.ReadAsStringAsync(); + Assert.That(claim.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(1)); + Assert.That(rewardList[0].GetProperty("reward_type").GetInt32(), Is.EqualTo(9)); // Rupy + + // Second claim returns OK with empty reward_list (idempotent — not 400) + var second = await client.PostAsync("/leader_skin/buy_set_item", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9001}""")); + var secondBody = await second.Content.ReadAsStringAsync(); + Assert.That(second.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + using var doc2 = JsonDocument.Parse(secondBody); + Assert.That(doc2.RootElement.GetProperty("reward_list").GetArrayLength(), Is.EqualTo(0)); + } + + [Test] + public async Task Ids_returns_owned_leader_skin_ids() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await SeedShop(factory); + await SetViewerCurrency(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + // Initial state: no owned skins from our shop + var beforeResp = await client.PostAsync("/leader_skin/ids", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""")); + var beforeBody = await beforeResp.Content.ReadAsStringAsync(); + using var beforeDoc = JsonDocument.Parse(beforeBody); + bool ownsBefore = false; + foreach (var id in beforeDoc.RootElement.GetProperty("user_leader_skin_ids").EnumerateArray()) + if (id.GetInt32() == 9101) { ownsBefore = true; break; } + Assert.That(ownsBefore, Is.False); + + // Buy skin 9101 + await client.PostAsync("/leader_skin/buy", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","product_id":90011,"sales_type":1,"item_id":null}""")); + + var afterResp = await client.PostAsync("/leader_skin/ids", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""")); + var afterBody = await afterResp.Content.ReadAsStringAsync(); + using var afterDoc = JsonDocument.Parse(afterBody); + bool ownsAfter = false; + foreach (var id in afterDoc.RootElement.GetProperty("user_leader_skin_ids").EnumerateArray()) + if (id.GetInt32() == 9101) { ownsAfter = true; break; } + Assert.That(ownsAfter, Is.True); + } + + [Test] + public async Task BuySet_on_series_without_set_sale_rejects() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + using (var scope = factory.Services.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + db.LeaderSkinShopSeries.Add(new LeaderSkinShopSeriesEntry + { + Id = 9999, IsEnabled = true, SetSalesStatus = 0, // no set sale + Products = { new LeaderSkinShopProductEntry { Id = 99991, SeriesId = 9999, LeaderSkinId = 1, IsEnabled = true, SinglePriceCrystal = 500 } }, + }); + await db.SaveChangesAsync(); + } + await SetViewerCurrency(factory, viewerId, crystals: 1000); + + using var client = factory.CreateAuthenticatedClient(viewerId); + var response = await client.PostAsync("/leader_skin/buy_set", + JsonBody("""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","series_id":9999,"sales_type":1,"item_id":null}""")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } +} diff --git a/SVSim.UnitTests/Importers/LeaderSkinShopImporterTests.cs b/SVSim.UnitTests/Importers/LeaderSkinShopImporterTests.cs new file mode 100644 index 0000000..1ae7f6a --- /dev/null +++ b/SVSim.UnitTests/Importers/LeaderSkinShopImporterTests.cs @@ -0,0 +1,65 @@ +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 LeaderSkinShopImporterTests +{ + private static string SeedDir => Path.Combine(AppContext.BaseDirectory, "Data", "seeds"); + + [Test] + public async Task Imports_series_products_and_set_rewards_from_seed_file() + { + using var factory = new SVSimTestFactory(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + await new LeaderSkinShopImporter().ImportAsync(db, SeedDir); + + var series = await db.LeaderSkinShopSeries + .Include(s => s.SetCompletionRewards) + .Include(s => s.Products).ThenInclude(p => p.Rewards) + .OrderBy(s => s.Id) + .ToListAsync(); + + Assert.That(series.Count, Is.GreaterThan(0)); + + // Spot-check series 100 (Shingeki no Bahamut) — set sale active with 2000c/2000r set price + var s100 = series.First(s => s.Id == 100); + Assert.That(s100.SetSalesStatus, Is.EqualTo(1)); + Assert.That(s100.SetPriceCrystal, Is.EqualTo(2000)); + Assert.That(s100.SetPriceRupy, Is.EqualTo(2000)); + Assert.That(s100.Products.Count, Is.GreaterThan(0)); + + // Spot-check a series with set-completion rewards (series 103 in capture has 2) + var withRewards = series.FirstOrDefault(s => s.SetCompletionRewards.Count > 0); + Assert.That(withRewards, Is.Not.Null, "at least one series should have set-completion rewards"); + Assert.That(withRewards!.SetCompletionRewards.All(r => r.RewardDetailId > 0), Is.True); + + // Per-product rewards (the captured shape — skin + emblem + sleeve triplet) + var firstProduct = s100.Products.OrderBy(p => p.Id).First(); + Assert.That(firstProduct.LeaderSkinId, Is.GreaterThan(0)); + Assert.That(firstProduct.Rewards.Count, Is.EqualTo(3)); + } + + [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 LeaderSkinShopImporter().ImportAsync(db, SeedDir); + int seriesBefore = await db.LeaderSkinShopSeries.CountAsync(); + int productsBefore = await db.LeaderSkinShopProducts.CountAsync(); + + await new LeaderSkinShopImporter().ImportAsync(db, SeedDir); + + Assert.That(await db.LeaderSkinShopSeries.CountAsync(), Is.EqualTo(seriesBefore)); + Assert.That(await db.LeaderSkinShopProducts.CountAsync(), Is.EqualTo(productsBefore)); + } +} diff --git a/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs b/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs index 90d6c7a..405766c 100644 --- a/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs +++ b/SVSim.UnitTests/Infrastructure/SVSimTestFactory.cs @@ -211,6 +211,7 @@ internal sealed class SVSimTestFactory : WebApplicationFactory await new ItemImporter().ImportAsync(ctx, seedDir); await new SleeveShopImporter().ImportAsync(ctx, seedDir); await new ItemPurchaseImporter().ImportAsync(ctx, seedDir); + await new LeaderSkinShopImporter().ImportAsync(ctx, seedDir); var puzzleImporter = new PuzzleImporter(); await puzzleImporter.ImportGroupsAsync(ctx, seedDir); await puzzleImporter.ImportPuzzlesAsync(ctx, seedDir);