diff --git a/database_migrations/000021_mock_sync_group_listing_drafts.down.sql b/database_migrations/000021_mock_sync_group_listing_drafts.down.sql new file mode 100644 index 0000000..e739134 --- /dev/null +++ b/database_migrations/000021_mock_sync_group_listing_drafts.down.sql @@ -0,0 +1,20 @@ +BEGIN; + + +DROP TABLE mock.shop_amazon_sync_group_listing_drafts; +DROP TABLE mock.shop_big_cartel_sync_group_listing_drafts; +DROP TABLE mock.shop_ebay_sync_group_listing_drafts; +DROP TABLE mock.shop_ecwid_sync_group_listing_drafts; +DROP TABLE mock.shop_etsy_sync_group_listing_drafts; +DROP TABLE mock.shop_shopify_sync_group_listing_drafts; +DROP TABLE mock.shop_square_online_sync_group_listing_drafts; +DROP TABLE mock.shop_squarespace_sync_group_listing_drafts; +DROP TABLE mock.shop_tiktok_sync_group_listing_drafts; +DROP TABLE mock.shop_walmart_marketplace_sync_group_listing_drafts; +DROP TABLE mock.shop_wix_sync_group_listing_drafts; +DROP TABLE mock.shop_woo_commerce_sync_group_listing_drafts; +DROP TABLE mock.shop_zoho_sync_group_listing_drafts; +DROP TABLE mock.sync_group_listing_draft_order_indexes; + + +COMMIT; diff --git a/database_migrations/000021_mock_sync_group_listing_drafts.up.sql b/database_migrations/000021_mock_sync_group_listing_drafts.up.sql new file mode 100644 index 0000000..23c186a --- /dev/null +++ b/database_migrations/000021_mock_sync_group_listing_drafts.up.sql @@ -0,0 +1,155 @@ +BEGIN; + + +CREATE TABLE mock.sync_group_listing_draft_order_indexes ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL CHECK (order_index >= 0), + + PRIMARY KEY (account_id, order_index) +); + +CREATE TABLE mock.shop_amazon_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_big_cartel_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_ebay_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_ecwid_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_etsy_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_shopify_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_square_online_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_squarespace_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_tiktok_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_walmart_marketplace_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_wix_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_woo_commerce_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + +CREATE TABLE mock.shop_zoho_sync_group_listing_drafts ( + account_id INTEGER NOT NULL REFERENCES mock.accounts, + order_index INTEGER NOT NULL, + shop_id TEXT NOT NULL, + listing_id TEXT, + + PRIMARY KEY (account_id, order_index), + UNIQUE (account_id, shop_id), + FOREIGN KEY (account_id, order_index) REFERENCES mock.sync_group_listing_draft_order_indexes ON DELETE CASCADE +); + + +COMMIT; diff --git a/diagrams/database_schema_mock.svg b/diagrams/database_schema_mock.svg index ead62c9..094b2b9 100644 --- a/diagrams/database_schema_mock.svg +++ b/diagrams/database_schema_mock.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/diagrams/database_schema_mock.uml b/diagrams/database_schema_mock.uml index eec866a..85c73d5 100644 --- a/diagrams/database_schema_mock.uml +++ b/diagrams/database_schema_mock.uml @@ -18,7 +18,7 @@ entity "**shop_amazon**" { } entity "**shop_amazon_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -28,6 +28,15 @@ entity "**shop_amazon_listings**" { *""description"": //text // } +entity "**shop_amazon_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_amazon_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -46,7 +55,7 @@ entity "**shop_big_cartel**" { } entity "**shop_big_cartel_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -56,6 +65,15 @@ entity "**shop_big_cartel_listings**" { *""description"": //text // } +entity "**shop_big_cartel_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_big_cartel_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -74,7 +92,7 @@ entity "**shop_ebay**" { } entity "**shop_ebay_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -84,6 +102,15 @@ entity "**shop_ebay_listings**" { *""description"": //text // } +entity "**shop_ebay_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_ebay_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -102,7 +129,7 @@ entity "**shop_ecwid**" { } entity "**shop_ecwid_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -112,6 +139,15 @@ entity "**shop_ecwid_listings**" { *""description"": //text // } +entity "**shop_ecwid_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_ecwid_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -130,7 +166,7 @@ entity "**shop_etsy**" { } entity "**shop_etsy_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -140,13 +176,22 @@ entity "**shop_etsy_listings**" { *""description"": //text // } +entity "**shop_etsy_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_etsy_sync_group_listings**" { -- *""account_id"": //integer [FK]// *""sync_group_id"": //integer [FK]// *""shop_id"": //text [FK]// *""listing_id"": //text // - *""order_index"": //integer // + *""order_index"": //integer [FK]// } entity "**shop_shopify**" { @@ -158,7 +203,7 @@ entity "**shop_shopify**" { } entity "**shop_shopify_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -168,13 +213,22 @@ entity "**shop_shopify_listings**" { *""description"": //text // } +entity "**shop_shopify_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_shopify_sync_group_listings**" { -- *""account_id"": //integer [FK]// *""sync_group_id"": //integer [FK]// *""shop_id"": //text [FK]// - *""listing_id"": //text // - *""order_index"": //integer // + *""listing_id"": //text [FK]// + *""order_index"": //integer [FK]// } entity "**shop_square_online**" { @@ -186,7 +240,7 @@ entity "**shop_square_online**" { } entity "**shop_square_online_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -196,6 +250,15 @@ entity "**shop_square_online_listings**" { *""description"": //text // } +entity "**shop_square_online_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_square_online_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -214,7 +277,7 @@ entity "**shop_squarespace**" { } entity "**shop_squarespace_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -224,6 +287,15 @@ entity "**shop_squarespace_listings**" { *""description"": //text // } +entity "**shop_squarespace_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_squarespace_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -242,7 +314,7 @@ entity "**shop_tiktok**" { } entity "**shop_tiktok_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -252,6 +324,15 @@ entity "**shop_tiktok_listings**" { *""description"": //text // } +entity "**shop_tiktok_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_tiktok_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -270,7 +351,7 @@ entity "**shop_walmart_marketplace**" { } entity "**shop_walmart_marketplace_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -280,12 +361,21 @@ entity "**shop_walmart_marketplace_listings**" { *""description"": //text // } +entity "**shop_walmart_marketplace_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_walmart_marketplace_sync_group_listings**" { -- *""account_id"": //integer [FK]// *""sync_group_id"": //integer [FK]// *""shop_id"": //text [FK]// - *""listing_id"": //text // + *""listing_id"": //text [FK]// *""order_index"": //integer // } @@ -298,7 +388,7 @@ entity "**shop_wix**" { } entity "**shop_wix_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -308,6 +398,15 @@ entity "**shop_wix_listings**" { *""description"": //text // } +entity "**shop_wix_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_wix_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -326,7 +425,7 @@ entity "**shop_woo_commerce**" { } entity "**shop_woo_commerce_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -336,6 +435,15 @@ entity "**shop_woo_commerce_listings**" { *""description"": //text // } +entity "**shop_woo_commerce_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK][FK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_woo_commerce_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -354,7 +462,7 @@ entity "**shop_zoho**" { } entity "**shop_zoho_listings**" { - + ""shop_id"": //text [PK]// + + ""shop_id"": //text [PK][FK]// + ""listing_id"": //text [PK]// -- *""account_id"": //integer [FK]// @@ -364,6 +472,15 @@ entity "**shop_zoho_listings**" { *""description"": //text // } +entity "**shop_zoho_sync_group_listing_drafts**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK]// + -- + *""account_id"": //integer // + *""shop_id"": //text // + ""listing_id"": //text // +} + entity "**shop_zoho_sync_group_listings**" { -- *""account_id"": //integer [FK]// @@ -373,6 +490,12 @@ entity "**shop_zoho_sync_group_listings**" { *""order_index"": //integer // } +entity "**sync_group_listing_draft_order_indexes**" { + + ""account_id"": //integer [PK][FK]// + + ""order_index"": //integer [PK]// + -- +} + entity "**sync_group_listing_order_indexes**" { -- *""sync_group_id"": //integer [FK]// @@ -396,7 +519,11 @@ entity "**sync_groups**" { "**shop_amazon_listings**" }-- "**accounts**" -"**shop_amazon_listings**" }-- "**shop_amazon**" +"**shop_amazon_listings**" ||-|| "**shop_amazon**" + +"**shop_amazon_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_amazon_sync_group_listing_drafts**" }-- "**accounts**" "**shop_amazon_sync_group_listings**" }-- "**accounts**" @@ -416,7 +543,11 @@ entity "**sync_groups**" { "**shop_big_cartel_listings**" }-- "**accounts**" -"**shop_big_cartel_listings**" }-- "**shop_big_cartel**" +"**shop_big_cartel_listings**" ||-|| "**shop_big_cartel**" + +"**shop_big_cartel_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_big_cartel_sync_group_listing_drafts**" }-- "**accounts**" "**shop_big_cartel_sync_group_listings**" }-- "**sync_group_listing_order_indexes**" @@ -436,7 +567,11 @@ entity "**sync_groups**" { "**shop_ebay_listings**" }-- "**accounts**" -"**shop_ebay_listings**" }-- "**shop_ebay**" +"**shop_ebay_listings**" ||-|| "**shop_ebay**" + +"**shop_ebay_sync_group_listing_drafts**" }-- "**accounts**" + +"**shop_ebay_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" "**shop_ebay_sync_group_listings**" }-- "**accounts**" @@ -456,7 +591,11 @@ entity "**sync_groups**" { "**shop_ecwid_listings**" }-- "**accounts**" -"**shop_ecwid_listings**" }-- "**shop_ecwid**" +"**shop_ecwid_listings**" ||-|| "**shop_ecwid**" + +"**shop_ecwid_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_ecwid_sync_group_listing_drafts**" }-- "**accounts**" "**shop_ecwid_sync_group_listings**" }-- "**accounts**" @@ -476,7 +615,11 @@ entity "**sync_groups**" { "**shop_etsy_listings**" }-- "**accounts**" -"**shop_etsy_listings**" }-- "**shop_etsy**" +"**shop_etsy_listings**" ||-|| "**shop_etsy**" + +"**shop_etsy_sync_group_listing_drafts**" }-- "**accounts**" + +"**shop_etsy_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" "**shop_etsy_sync_group_listings**" }-- "**accounts**" @@ -496,7 +639,11 @@ entity "**sync_groups**" { "**shop_shopify_listings**" }-- "**accounts**" -"**shop_shopify_listings**" }-- "**shop_shopify**" +"**shop_shopify_listings**" ||-|| "**shop_shopify**" + +"**shop_shopify_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_shopify_sync_group_listing_drafts**" }-- "**accounts**" "**shop_shopify_sync_group_listings**" }-- "**accounts**" @@ -516,7 +663,11 @@ entity "**sync_groups**" { "**shop_square_online_listings**" }-- "**accounts**" -"**shop_square_online_listings**" }-- "**shop_square_online**" +"**shop_square_online_listings**" ||-|| "**shop_square_online**" + +"**shop_square_online_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_square_online_sync_group_listing_drafts**" }-- "**accounts**" "**shop_square_online_sync_group_listings**" }-- "**sync_group_listing_order_indexes**" @@ -536,7 +687,11 @@ entity "**sync_groups**" { "**shop_squarespace_listings**" }-- "**accounts**" -"**shop_squarespace_listings**" }-- "**shop_squarespace**" +"**shop_squarespace_listings**" ||-|| "**shop_squarespace**" + +"**shop_squarespace_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_squarespace_sync_group_listing_drafts**" }-- "**accounts**" "**shop_squarespace_sync_group_listings**" }-- "**sync_group_listing_order_indexes**" @@ -556,7 +711,11 @@ entity "**sync_groups**" { "**shop_tiktok_listings**" }-- "**accounts**" -"**shop_tiktok_listings**" }-- "**shop_tiktok**" +"**shop_tiktok_listings**" ||-|| "**shop_tiktok**" + +"**shop_tiktok_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_tiktok_sync_group_listing_drafts**" }-- "**accounts**" "**shop_tiktok_sync_group_listings**" }-- "**accounts**" @@ -576,7 +735,11 @@ entity "**sync_groups**" { "**shop_walmart_marketplace_listings**" }-- "**accounts**" -"**shop_walmart_marketplace_listings**" }-- "**shop_walmart_marketplace**" +"**shop_walmart_marketplace_listings**" ||-|| "**shop_walmart_marketplace**" + +"**shop_walmart_marketplace_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_walmart_marketplace_sync_group_listing_drafts**" }-- "**accounts**" "**shop_walmart_marketplace_sync_group_listings**" }-- "**sync_group_listing_order_indexes**" @@ -596,7 +759,11 @@ entity "**sync_groups**" { "**shop_wix_listings**" }-- "**accounts**" -"**shop_wix_listings**" }-- "**shop_wix**" +"**shop_wix_listings**" ||-|| "**shop_wix**" + +"**shop_wix_sync_group_listing_drafts**" }-- "**accounts**" + +"**shop_wix_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" "**shop_wix_sync_group_listings**" }-- "**accounts**" @@ -616,7 +783,11 @@ entity "**sync_groups**" { "**shop_woo_commerce_listings**" }-- "**accounts**" -"**shop_woo_commerce_listings**" }-- "**shop_woo_commerce**" +"**shop_woo_commerce_listings**" ||-|| "**shop_woo_commerce**" + +"**shop_woo_commerce_sync_group_listing_drafts**" ||-|| "**sync_group_listing_draft_order_indexes**" + +"**shop_woo_commerce_sync_group_listing_drafts**" }-- "**accounts**" "**shop_woo_commerce_sync_group_listings**" }-- "**sync_group_listing_order_indexes**" @@ -636,7 +807,11 @@ entity "**sync_groups**" { "**shop_zoho_listings**" }-- "**accounts**" -"**shop_zoho_listings**" }-- "**shop_zoho**" +"**shop_zoho_listings**" ||-|| "**shop_zoho**" + +"**shop_zoho_sync_group_listing_drafts**" }-- "**accounts**" + +"**shop_zoho_sync_group_listing_drafts**" }-- "**sync_group_listing_draft_order_indexes**" "**shop_zoho_sync_group_listings**" }-- "**accounts**" @@ -648,6 +823,8 @@ entity "**sync_groups**" { "**shop_zoho_sync_group_listings**" }-- "**sync_group_listing_order_indexes**" +"**sync_group_listing_draft_order_indexes**" }-- "**accounts**" + "**sync_group_listing_order_indexes**" }-- "**sync_groups**" "**sync_groups**" }-- "**accounts**" diff --git a/diagrams/database_schema_public.svg b/diagrams/database_schema_public.svg index bffc4c1..9534d04 100644 --- a/diagrams/database_schema_public.svg +++ b/diagrams/database_schema_public.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/diagrams/database_schema_public.uml b/diagrams/database_schema_public.uml index 69f4700..fe895ab 100644 --- a/diagrams/database_schema_public.uml +++ b/diagrams/database_schema_public.uml @@ -38,10 +38,10 @@ entity "**etsy_oauth_requests**" { entity "**etsy_store_events**" { + ""store_id"": //text [PK]// - + ""event_timestamp"": //timestamp with time zone [PK]// + + ""event_timestamp"": //timestamp with time zone [PK][FK]// + ""event_id"": //text [PK]// -- - *""platform"": //text [FK]// + *""platform"": //text // } entity "**etsy_users**" { @@ -125,18 +125,18 @@ entity "**sync_groups**" { entity "**tiktok_store_events**" { + ""store_id"": //text [PK]// - + ""event_timestamp"": //timestamp with time zone [PK]// + + ""event_timestamp"": //timestamp with time zone [PK][FK]// + ""event_id"": //text [PK]// -- - *""platform"": //text [FK]// + *""platform"": //text // } entity "**wix_store_events**" { + ""store_id"": //text [PK]// - + ""event_timestamp"": //timestamp with time zone [PK]// + + ""event_timestamp"": //timestamp with time zone [PK][FK]// + ""event_id"": //text [PK]// -- - *""platform"": //text [FK]// + *""platform"": //text // } "**accounts**" }-- "**oauth_users**" @@ -145,7 +145,7 @@ entity "**wix_store_events**" { "**etsy_oauth_requests**" }-- "**accounts**" -"**etsy_store_events**" }-- "**raw_store_events**" +"**etsy_store_events**" ||-|| "**raw_store_events**" "**etsy_users**" }-- "**accounts**" @@ -157,7 +157,7 @@ entity "**wix_store_events**" { "**sync_groups**" }-- "**accounts**" -"**tiktok_store_events**" }-- "**raw_store_events**" +"**tiktok_store_events**" ||-|| "**raw_store_events**" -"**wix_store_events**" }-- "**raw_store_events**" +"**wix_store_events**" ||-|| "**raw_store_events**" @enduml diff --git a/internal/domains/accounts/accounts.go b/internal/domains/accounts/accounts.go index fe18bcf..22b775e 100644 --- a/internal/domains/accounts/accounts.go +++ b/internal/domains/accounts/accounts.go @@ -277,6 +277,63 @@ func (db *Store) GetShops(ctx context.Context, acctID int64) ([]AccountShop, err return shops, nil } +func (db *Store) GetMockShops(ctx context.Context, acctID int64) ([]AccountShop, error) { + infos := getAllMockShopSchemaInfos() + lists := make([][]AccountShop, len(infos)) + numShops := 0 + + for i, in := range infos { + rows, err := db.db.Query( + ctx, + fmt.Sprintf( + ` + SELECT + shop_id, + name + FROM + %s + WHERE + account_id = @account_id + `, + in.shopTable, + ), + pgx.NamedArgs{ + "account_id": acctID, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to perform query: %w", err) + } + + vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct { + Shop_id string + Name string + }]) + if err != nil { + return nil, fmt.Errorf("failed to scan rows: %w", err) + } + + list := make([]AccountShop, len(vs)) + for i, v := range vs { + list[i] = AccountShop{ + AccountShopIDs: NewAccountIDs(acctID). + ShopID(in.platform, v.Shop_id), + Name: v.Name, + } + } + + lists[i] = list + numShops += len(list) + } + + shops := make([]AccountShop, 0, numShops) + for _, list := range lists { + shops = append(shops, list...) + } + + return shops, nil +} + func (db *Store) GetListingsForShop(ctx context.Context, acctID int64, platform Platform, shopID string) ([]Listing, error) { var vs []Listing for _, v := range getDevListings(acctID) { @@ -292,6 +349,66 @@ func (db *Store) GetListingsForShop(ctx context.Context, acctID int64, platform return vs, nil } +func (db *Store) GetMockListingsForShop(ctx context.Context, acctID int64, platform Platform, shopID string) ([]Listing, error) { + in, ok := getMockShopSchemaInfo(platform) + if !ok { + return nil, consts.ErrNotFound + } + + rows, err := db.db.Query( + ctx, + fmt.Sprintf( + ` + SELECT + listing_id, + sku, + name, + description, + "count" + FROM + %s + WHERE + account_id = @account_id + AND shop_id = @shop_id + `, + in.listingsTable, + ), + pgx.NamedArgs{ + "account_id": acctID, + "shop_id": shopID, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to perform query: %w", err) + } + + vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct { + Listing_id string + SKU string + Name string + Description string + Count int64 + }]) + if err != nil { + return nil, fmt.Errorf("failed to scan rows: %w", err) + } + + listings := make([]Listing, len(vs)) + for i, v := range vs { + listings[i] = Listing{ + AccountShopListingIDs: NewAccountIDs(acctID). + ShopID(in.platform, shopID). + ListingID(v.Listing_id), + SKU: v.SKU, + Name: v.Name, + Description: v.Description, + Count: v.Count, + } + } + + return listings, nil +} + // additional context func (db *Store) GetAccountPointerByUserID(ctx context.Context, userID string) (*Account, error) { diff --git a/internal/domains/accounts/mocks.go b/internal/domains/accounts/mocks.go index 0add306..35e1aa8 100644 --- a/internal/domains/accounts/mocks.go +++ b/internal/domains/accounts/mocks.go @@ -412,28 +412,19 @@ func (db *Store) ListMockListingsForShop(ctx context.Context, acctID int64, plat ctx, fmt.Sprintf( ` - WITH shop_exists(shop_exists) AS ( - SELECT - true - FROM - %s - WHERE - account_id = @account_id - AND shop_id = @shop_id - ) SELECT - COALESCE(shop_exists, false) AS shop_exists, - listing_id, - sku, - name, - description, - "count" + true AS shop_exists, + l.listing_id, + l.sku, + l.name, + l.description, + l."count" FROM - shop_exists + %s AS s LEFT JOIN - %s - ON - TRUE + %s AS l + USING + (account_id, shop_id) WHERE (account_id IS NULL OR account_id = @account_id) AND (shop_id IS NULL OR shop_id = @shop_id) @@ -466,11 +457,11 @@ func (db *Store) ListMockListingsForShop(ctx context.Context, acctID int64, plat // check if the shop exists, and confirm there were actually listings (LEFT JOIN) - if len(vs) == 0 || !vs[0].Shop_exists { - fmt.Println("TEST") + if len(vs) == 0 { return nil, fmt.Errorf("%w: shop not found", consts.ErrNotFound) } if !vs[0].SKU.Valid { + // shop exists, but there are not listings return nil, nil } @@ -530,6 +521,106 @@ func getMockShopTableName(platform Platform) (string, error) { } } +type mockShopSchemaInfo struct { + platform Platform + shopTable string + listingsTable string + syncGroupListingDraftsTable string +} + +func getMockShopSchemaInfo(platform Platform) (mockShopSchemaInfo, bool) { + for _, in := range getAllMockShopSchemaInfos() { + if in.platform == platform { + return in, true + } + } + + return mockShopSchemaInfo{}, false +} + +func getAllMockShopSchemaInfos() []mockShopSchemaInfo { + return []mockShopSchemaInfo{ + { + platform: Amazon, + shopTable: "mock.shop_amazon", + listingsTable: "mock.shop_amazon_listings", + syncGroupListingDraftsTable: "mock.shop_amazon_sync_group_listing_drafts", + }, + { + platform: BigCartel, + shopTable: "mock.shop_big_cartel", + listingsTable: "mock.shop_big_cartel_listings", + syncGroupListingDraftsTable: "mock.shop_big_cartel_sync_group_listing_drafts", + }, + { + platform: Ebay, + shopTable: "mock.shop_ebay", + listingsTable: "mock.shop_ebay_listings", + syncGroupListingDraftsTable: "mock.shop_ebay_sync_group_listing_drafts", + }, + { + platform: Ecwid, + shopTable: "mock.shop_ecwid", + listingsTable: "mock.shop_ecwid_listings", + syncGroupListingDraftsTable: "mock.shop_ecwid_sync_group_listing_drafts", + }, + { + platform: Etsy, + shopTable: "mock.shop_etsy", + listingsTable: "mock.shop_etsy_listings", + syncGroupListingDraftsTable: "mock.shop_etsy_sync_group_listing_drafts", + }, + { + platform: Shopify, + shopTable: "mock.shop_shopify", + listingsTable: "mock.shop_shopify_listings", + syncGroupListingDraftsTable: "mock.shop_shopify_sync_group_listing_drafts", + }, + { + platform: SquareOnline, + shopTable: "mock.shop_square_online", + listingsTable: "mock.shop_square_online_listings", + syncGroupListingDraftsTable: "mock.shop_square_online_sync_group_listing_drafts", + }, + { + platform: Squarespace, + shopTable: "mock.shop_squarespace", + listingsTable: "mock.shop_squarespace_listings", + syncGroupListingDraftsTable: "mock.shop_squarespace_sync_group_listing_drafts", + }, + { + platform: Tiktok, + shopTable: "mock.shop_tiktok", + listingsTable: "mock.shop_tiktok_listings", + syncGroupListingDraftsTable: "mock.shop_tiktok_sync_group_listing_drafts", + }, + { + platform: WalmartMarketplace, + shopTable: "mock.shop_walmart_marketplace", + listingsTable: "mock.shop_walmart_marketplace_listings", + syncGroupListingDraftsTable: "mock.shop_walmart_marketplace_sync_group_listing_drafts", + }, + { + platform: Wix, + shopTable: "mock.shop_wix", + listingsTable: "mock.shop_wix_listings", + syncGroupListingDraftsTable: "mock.shop_wix_sync_group_listing_drafts", + }, + { + platform: WooCommerce, + shopTable: "mock.shop_woo_commerce", + listingsTable: "mock.shop_woo_commerce_listings", + syncGroupListingDraftsTable: "mock.shop_woo_commerce_sync_group_listing_drafts", + }, + { + platform: Zoho, + shopTable: "mock.shop_zoho", + listingsTable: "mock.shop_zoho_listings", + syncGroupListingDraftsTable: "mock.shop_zoho_sync_group_listing_drafts", + }, + } +} + func getMockShopAndListingsTableNames(platform Platform) (shop string, listing string, err error) { shop, err = getMockShopTableName(platform) if err != nil { diff --git a/internal/domains/accounts/store_with_context.go b/internal/domains/accounts/store_with_context.go index e9f43dd..de3626f 100644 --- a/internal/domains/accounts/store_with_context.go +++ b/internal/domains/accounts/store_with_context.go @@ -44,10 +44,18 @@ func (v_ctx *StoreWithContext) GetShops(acctID int64) ([]AccountShop, error) { return v_ctx.Store.GetShops(v_ctx.ctx, acctID) } +func (v_ctx *StoreWithContext) GetMockShops(acctID int64) ([]AccountShop, error) { + return v_ctx.Store.GetMockShops(v_ctx.ctx, acctID) +} + func (v_ctx *StoreWithContext) GetListingsForShop(acctID int64, platform Platform, shopID string) ([]Listing, error) { return v_ctx.Store.GetListingsForShop(v_ctx.ctx, acctID, platform, shopID) } +func (v_ctx *StoreWithContext) GetMockListingsForShop(acctID int64, platform Platform, shopID string) ([]Listing, error) { + return v_ctx.Store.GetMockListingsForShop(v_ctx.ctx, acctID, platform, shopID) +} + func (v_ctx *StoreWithContext) GetAccountPointerByUserID(userID string) (*Account, error) { return v_ctx.Store.GetAccountPointerByUserID(v_ctx.ctx, userID) } @@ -96,26 +104,50 @@ func (v_ctx *StoreWithContext) CreateSyncGroupListingDraft(acctID int64) (int, e return v_ctx.Store.CreateSyncGroupListingDraft(v_ctx.ctx, acctID) } +func (v_ctx *StoreWithContext) CreateMockSyncGroupListingDraft(acctID int64) (int, error) { + return v_ctx.Store.CreateMockSyncGroupListingDraft(v_ctx.ctx, acctID) +} + func (v_ctx *StoreWithContext) GetSyncGroupListingDraft(acctID int64, orderIndex int) (SyncGroupListingDraft, error) { return v_ctx.Store.GetSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex) } +func (v_ctx *StoreWithContext) GetMockSyncGroupListingDraft(acctID int64, orderIndex int) (SyncGroupListingDraft, error) { + return v_ctx.Store.GetMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex) +} + func (v_ctx *StoreWithContext) SetShopInSyncGroupListingDraft(acctID int64, orderIndex int, platform Platform, shopID string) error { return v_ctx.Store.SetShopInSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, platform, shopID) } +func (v_ctx *StoreWithContext) SetShopInMockSyncGroupListingDraft(acctID int64, orderIndex int, platform Platform, shopID string) error { + return v_ctx.Store.SetShopInMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, platform, shopID) +} + func (v_ctx *StoreWithContext) SetListingInSyncGroupListingDraft(acctID int64, orderIndex int, listingID string) error { return v_ctx.Store.SetListingInSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, listingID) } +func (v_ctx *StoreWithContext) SetListingInMockSyncGroupListingDraft(acctID int64, orderIndex int, listingID string) error { + return v_ctx.Store.SetListingInMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex, listingID) +} + func (v_ctx *StoreWithContext) DeleteSyncGroupListingDraft(acctID int64, orderIndex int) (int, error) { return v_ctx.Store.DeleteSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex) } +func (v_ctx *StoreWithContext) DeleteMockSyncGroupListingDraft(acctID int64, orderIndex int) error { + return v_ctx.Store.DeleteMockSyncGroupListingDraft(v_ctx.ctx, acctID, orderIndex) +} + func (v_ctx *StoreWithContext) GetSyncGroupListingDrafts(acctID int64) ([]SyncGroupListingDraft, error) { return v_ctx.Store.GetSyncGroupListingDrafts(v_ctx.ctx, acctID) } +func (v_ctx *StoreWithContext) GetMockSyncGroupListingDrafts(acctID int64) ([]SyncGroupListingDraft, error) { + return v_ctx.Store.GetMockSyncGroupListingDrafts(v_ctx.ctx, acctID) +} + func (v_ctx *StoreWithContext) SaveNewSyncGroup(acctID int64) (SyncGroup, error) { return v_ctx.Store.SaveNewSyncGroup(v_ctx.ctx, acctID) } diff --git a/internal/domains/accounts/sync_groups.go b/internal/domains/accounts/sync_groups.go index 6867702..6698721 100644 --- a/internal/domains/accounts/sync_groups.go +++ b/internal/domains/accounts/sync_groups.go @@ -8,6 +8,7 @@ import ( "slices" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" ) type ( @@ -28,6 +29,7 @@ type ( SyncGroupID int64 } + // TODO: the id ACTUALLY is the account_id and order_index SyncGroupListingDraft struct { AccountShopIDs ListingID string @@ -75,6 +77,44 @@ func (db *Store) CreateSyncGroupListingDraft(ctx context.Context, acctID int64) return orderIndex, nil } +func (db *Store) CreateMockSyncGroupListingDraft(ctx context.Context, acctID int64) (orderIndex int, err error) { + rows, err := db.db.Query( + ctx, + ` + WITH max_order_index(max_index) AS ( + SELECT + MAX(order_index) AS max_index + FROM + mock.sync_group_listing_draft_order_indexes + ) + INSERT INTO + mock.sync_group_listing_draft_order_indexes ( + account_id, + order_index + ) + SELECT + @account_id, + COALESCE(max_index, -1) + 1 + FROM + max_order_index + RETURNING + order_index + `, + pgx.NamedArgs{ + "account_id": acctID, + }, + ) + if err != nil { + return 0, fmt.Errorf("failed to perform query: %w", err) + } + + if orderIndex, err = pgx.CollectExactlyOneRow(rows, pgx.RowTo[int]); err != nil { + return 0, fmt.Errorf("failed to scan rows: %w", err) + } + + return orderIndex, nil +} + func (db *Store) GetSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) (SyncGroupListingDraft, error) { rows, err := db.db.Query( ctx, @@ -124,8 +164,96 @@ func (db *Store) GetSyncGroupListingDraft(ctx context.Context, acctID int64, ord }, nil } +func (db *Store) GetMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) (SyncGroupListingDraft, error) { + tx, err := db.db.BeginTx(ctx, pgx.TxOptions{ + AccessMode: pgx.ReadOnly, + }) + if err != nil { + return SyncGroupListingDraft{}, fmt.Errorf("failed to begin transaction: %w", err) + } + defer tx.Rollback(ctx) + + rows, err := tx.Query( + ctx, + ` + SELECT + true + FROM + mock.sync_group_listing_draft_order_indexes + WHERE + account_id = @account_id + AND order_index = @order_index + `, + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + }, + ) + if err != nil { + return SyncGroupListingDraft{}, fmt.Errorf("failed to scan rows: %w", err) + } + + if _, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[bool]); err != nil { + return SyncGroupListingDraft{}, fmt.Errorf("failed to scan rows: %w", err) + } + + for _, info := range getAllMockShopSchemaInfos() { + rows, err := tx.Query( + ctx, + fmt.Sprintf( + ` + SELECT + shop_id, + listing_id + FROM + %s + WHERE + account_id = @account_id + AND order_index = @order_index + `, + info.syncGroupListingDraftsTable, + ), + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + }, + ) + if err != nil { + return SyncGroupListingDraft{}, fmt.Errorf("failed to perform query: %w", err) + } + + v, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByNameLax[struct { + Shop_id pgtype.Text + Listing_id pgtype.Text + }]) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + continue + } + return SyncGroupListingDraft{}, fmt.Errorf("faild to scan row: %w", err) + } + + return SyncGroupListingDraft{ + AccountShopIDs: NewAccountIDs(acctID). + ShopID(info.platform, v.Shop_id.String), + ListingID: v.Listing_id.String, + OrderIndex: orderIndex, + }, nil + } + + if err := tx.Commit(ctx); err != nil { + return SyncGroupListingDraft{}, fmt.Errorf("failed to commit transaction: %w", err) + } + + return SyncGroupListingDraft{ + AccountShopIDs: NewAccountIDs(acctID). + ShopID("", ""), + OrderIndex: orderIndex, + }, nil +} + func (db *Store) SetShopInSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, platform Platform, shopID string) error { - tags, err := db.db.Exec( + tag, err := db.db.Exec( ctx, ` UPDATE @@ -148,15 +276,106 @@ func (db *Store) SetShopInSyncGroupListingDraft(ctx context.Context, acctID int6 if err != nil { return fmt.Errorf("failed to perform query: %w", err) } - if tags.RowsAffected() != 1 { + if tag.RowsAffected() != 1 { return consts.ErrNotFound } return nil } +func (db *Store) SetShopInMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, platform Platform, shopID string) error { + info, ok := getMockShopSchemaInfo(platform) + if !ok { + return fmt.Errorf("%w: unrecognized platform: %s", consts.ErrNotFound, platform) + } + + tx, err := db.db.Begin(ctx) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer tx.Rollback(ctx) + + _, err = tx.Exec( + ctx, + ` + DELETE FROM + mock.sync_group_listing_draft_order_indexes + WHERE + account_id = @account_id + AND order_index = @order_index + RETURNING + account_id, + order_index + `, + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + }, + ) + if err != nil { + return fmt.Errorf("failed to perform query deleting existing index: %w", err) + } + + _, err = tx.Exec( + ctx, + ` + INSERT INTO + mock.sync_group_listing_draft_order_indexes ( + account_id, + order_index + ) + VALUES ( + @account_id, + @order_index + ) + `, + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + }, + ) + if err != nil { + // TODO: handle error where account doesn't exist? + return fmt.Errorf("failed to perform query inserting new index: %w", err) + } + + _, err = tx.Exec( + ctx, + fmt.Sprintf( + ` + INSERT INTO + %s ( + account_id, + order_index, + shop_id + ) + VALUES ( + @account_id, + @order_index, + @shop_id + ) + `, + info.syncGroupListingDraftsTable, + ), + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + "shop_id": shopID, + }, + ) + if err != nil { + return fmt.Errorf("failed to perform query inserting specific shop index: %w", err) + } + + if err := tx.Commit(ctx); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + func (db *Store) SetListingInSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, listingID string) error { - tags, err := db.db.Exec( + tag, err := db.db.Exec( ctx, ` UPDATE @@ -176,13 +395,47 @@ func (db *Store) SetListingInSyncGroupListingDraft(ctx context.Context, acctID i if err != nil { return fmt.Errorf("failed to perform query: %w", err) } - if tags.RowsAffected() != 1 { + if tag.RowsAffected() != 1 { return consts.ErrNotFound } return nil } +func (db *Store) SetListingInMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int, listingID string) error { + for _, info := range getAllMockShopSchemaInfos() { + tag, err := db.db.Exec( + ctx, + fmt.Sprintf( + ` + UPDATE + %s + SET + listing_id = @listing_id + WHERE + account_id = @account_id + AND order_index = @order_index + `, + info.syncGroupListingDraftsTable, + ), + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + "listing_id": listingID, + }, + ) + if err != nil { + return fmt.Errorf("failed to perform query: %w", err) + } + + if tag.RowsAffected() > 0 { + return nil + } + } + + return consts.ErrNotFound +} + func (db *Store) DeleteSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) (numOfRows int, err error) { rows, err := db.db.Query( ctx, @@ -238,6 +491,31 @@ func (db *Store) DeleteSyncGroupListingDraft(ctx context.Context, acctID int64, return r.Num_rows, nil } +func (db *Store) DeleteMockSyncGroupListingDraft(ctx context.Context, acctID int64, orderIndex int) error { + tag, err := db.db.Exec( + ctx, + ` + DELETE FROM + mock.sync_group_listing_draft_order_indexes + WHERE + account_id = @account_id + AND order_index = @order_index + `, + pgx.NamedArgs{ + "account_id": acctID, + "order_index": orderIndex, + }, + ) + if err != nil { + return fmt.Errorf("failed to perform query: %w", err) + } + if tag.RowsAffected() != 1 { + return consts.ErrNotFound + } + + return nil +} + func (db *Store) GetSyncGroupListingDrafts(ctx context.Context, acctID int64) ([]SyncGroupListingDraft, error) { rows, err := db.db.Query( ctx, @@ -296,7 +574,105 @@ func (db *Store) GetSyncGroupListingDrafts(ctx context.Context, acctID int64) ([ return listings, nil } -// TODO: test +func (db *Store) GetMockSyncGroupListingDrafts(ctx context.Context, acctID int64) ([]SyncGroupListingDraft, error) { + tx, err := db.db.BeginTx(ctx, pgx.TxOptions{ + AccessMode: pgx.ReadOnly, + }) + if err != nil { + return nil, fmt.Errorf("failed to begin transaction: %w", err) + } + defer tx.Rollback(ctx) + + rows, err := tx.Query( + ctx, + ` + SELECT + order_index + FROM + mock.sync_group_listing_draft_order_indexes + WHERE + account_id = @account_id + `, + pgx.NamedArgs{ + "account_id": acctID, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to perform query: %w", err) + } + + indexes, err := pgx.CollectRows(rows, pgx.RowTo[int]) + if err != nil { + return nil, fmt.Errorf("failed to scan rows: %w", err) + } + + listingsByOrderIndex := make(map[int]SyncGroupListingDraft, len(indexes)) + for _, n := range indexes { + listingsByOrderIndex[n] = SyncGroupListingDraft{ + AccountShopIDs: NewAccountIDs(acctID). + ShopID("", ""), + OrderIndex: n, + } + } + + for _, info := range getAllMockShopSchemaInfos() { + rows, err := tx.Query( + ctx, + fmt.Sprintf( + ` + SELECT + shop_id, + order_index, + listing_id + FROM + %s + WHERE + account_id = @account_id + `, + info.syncGroupListingDraftsTable, + ), + pgx.NamedArgs{ + "account_id": acctID, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to perform query: %w", err) + } + + vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct { + Shop_id pgtype.Text + Order_index pgtype.Int8 + Listing_id pgtype.Text + }]) + if err != nil { + return nil, fmt.Errorf("failed to scan rows: %w", err) + } + + for _, v := range vs { + n := int(v.Order_index.Int64) + l := listingsByOrderIndex[n] + l.Platform = info.platform + l.ShopID = v.Shop_id.String + l.ListingID = v.Listing_id.String + listingsByOrderIndex[n] = l + } + } + + listings := make([]SyncGroupListingDraft, 0, len(listingsByOrderIndex)) + for _, v := range listingsByOrderIndex { + listings = append(listings, v) + } + slices.SortFunc(listings, func(a, b SyncGroupListingDraft) int { + return a.OrderIndex - b.OrderIndex + }) + + if err := tx.Commit(ctx); err != nil { + return nil, fmt.Errorf("failed to commit transaction: %w", err) + } + + return listings, nil +} + func (db *Store) SaveNewSyncGroup(ctx context.Context, acctID int64) (SyncGroup, error) { txn, err := db.db.Begin(ctx) if err != nil { diff --git a/internal/server/api/accounts/router.go b/internal/server/api/accounts/router.go index 83eaa35..380386a 100644 --- a/internal/server/api/accounts/router.go +++ b/internal/server/api/accounts/router.go @@ -54,6 +54,13 @@ func Routes( draftListings.PUT("/:orderIndex/shop", response.Handler(as.setShopInSyncGroupListingDraft)) draftListings.PUT("/:orderIndex/listing", response.Handler(as.setListingInSyncGroupListingDraft)) draftListings.DELETE("/:orderIndex", response.Handler(as.deleteSyncGroupListingDraft)) + + mockSyncGroups := syncGroups.Group("/mock") + mockDraftListings := mockSyncGroups.Group("/draft/listings", pub.Publish("/:acctID/inventory/sync-groups/mock/draft/listings")) + mockDraftListings.POST("", response.Handler(as.createMockSyncGroupListingDraft)) + mockDraftListings.PUT("/:orderIndex/shop", response.Handler(as.setShopInMockSyncGroupListingDraft)) + mockDraftListings.PUT("/:orderIndex/listing", response.Handler(as.setListingInMockSyncGroupListingDraft)) + mockDraftListings.DELETE("/:orderIndex", response.Handler(as.deleteMockSyncGroupListingDraft)) } // POST / @@ -110,6 +117,7 @@ func (s *accountSubrouter) setShopInSyncGroupListingDraft(c *gin.Context) (respo err := param.Path("orderIndex", param.Int(&orderIndex)). Form("platform", param.Platform(&platform)). + Form("shop-id", param.Text(&shopID)). Unmarshal(c) if err != nil { return nil, err @@ -388,6 +396,96 @@ func (s *accountSubrouter) deleteMockListing(c *gin.Context) (response.Response, return response.StatusNoContent(), nil } +// POST /:acctID/inventory/sync-groups/mock/draft/listings +func (s *accountSubrouter) createMockSyncGroupListingDraft(c *gin.Context) (response.Response, error) { + r := c.Request + ctx := r.Context() + acctID := auth.GetIdentity(ctx).Account.AccountID + + if _, err := s.accts.CreateMockSyncGroupListingDraft(ctx, acctID); err != nil { + return nil, response.Errorf("failed to create new listing draft: %w", err) + } + + return response.StatusCreated(), nil +} + +// PUT /:acctID/inventory/sync-groups/mock/draft/listings/:orderIndex/shop +// @platform string +// @shopID string +func (s *accountSubrouter) setShopInMockSyncGroupListingDraft(c *gin.Context) (response.Response, error) { + r := c.Request + ctx := r.Context() + acctID := auth.GetIdentity(ctx).Account.AccountID + + var ( + orderIndex int + platform accounts.Platform + shopID string + ) + + err := param.Path("orderIndex", param.Int(&orderIndex)). + Form("platform", param.Platform(&platform)). + Form("shop-id", param.Text(&shopID)). + Unmarshal(c) + if err != nil { + return nil, err + } + + if err := s.accts.SetShopInMockSyncGroupListingDraft(ctx, acctID, orderIndex, platform, shopID); err != nil { + return nil, response.Errorf("failed to set shop: %w", response.ErrorFromConstant(err)) + } + + return response.StatusOK(), nil +} + +// PUT /:acctID/inventory/sync-groups/mock/draft/listings/:orderIndex/listing +func (s *accountSubrouter) setListingInMockSyncGroupListingDraft(c *gin.Context) (response.Response, error) { + r := c.Request + ctx := r.Context() + acctID := auth.GetIdentity(ctx).Account.AccountID + + var ( + orderIndex int + listingID string + ) + + err := param.Path("orderIndex", param.Int(&orderIndex)). + Form("listing-id", param.Text(&listingID)). + Unmarshal(c) + if err != nil { + return nil, err + } + + if err := s.accts.SetListingInMockSyncGroupListingDraft(ctx, acctID, orderIndex, listingID); err != nil { + return nil, response.Errorf("failed to set listing: %w", response.ErrorFromConstant(err)) + } + + return response.StatusOK(), nil +} + +// DELETE /:acctID/inventory/sync-groups/mock/draft/listings/:orderIndex +func (s *accountSubrouter) deleteMockSyncGroupListingDraft(c *gin.Context) (response.Response, error) { + r := c.Request + ctx := r.Context() + acctID := auth.GetIdentity(ctx).Account.AccountID + + var ( + orderIndex int + ) + + err := param.Path("orderIndex", param.Int(&orderIndex)). + Unmarshal(c) + if err != nil { + return nil, err + } + + if err := s.accts.DeleteMockSyncGroupListingDraft(ctx, acctID, orderIndex); err != nil { + return nil, response.Errorf("failed to delete listing: %w", response.ErrorFromConstant(err)) + } + + return response.StatusOK(), nil +} + func lowerSnakeCase(s accounts.Platform) string { return strings.ToLower(strings.Join(strings.Split(string(s), " "), "_")) } diff --git a/styles/index.css b/styles/index.css index ce64533..d47d2b9 100644 --- a/styles/index.css +++ b/styles/index.css @@ -250,9 +250,6 @@ .mt-\[3em\] { margin-top: 3em; } - .mt-\[5em\] { - margin-top: 5em; - } .mb-\[0\.5em\] { margin-bottom: 0.5em; } @@ -295,18 +292,9 @@ .h-fit { height: fit-content; } - .max-h-\[10vh\] { - max-height: 10vh; - } - .max-h-\[50em\] { - max-height: 50em; - } .max-h-\[80vh\] { max-height: 80vh; } - .max-h-\[100vh\] { - max-height: 100vh; - } .max-h-full { max-height: 100%; } @@ -334,9 +322,6 @@ .flex-shrink { flex-shrink: 1; } - .flex-grow { - flex-grow: 1; - } .grow { flex-grow: 1; } @@ -364,9 +349,6 @@ .list-none { list-style-type: none; } - .grid-cols-\[2fr_8fr_2fr\] { - grid-template-columns: 2fr 8fr 2fr; - } .grid-cols-\[6rem_10fr_6rem\] { grid-template-columns: 6rem 10fr 6rem; } @@ -382,9 +364,6 @@ .flex-col { flex-direction: column; } - .content-center { - align-content: center; - } .items-center { align-items: center; } @@ -406,9 +385,6 @@ .gap-\[1em\] { gap: 1em; } - .gap-x-\[1em\] { - column-gap: 1em; - } .gap-y-\[2em\] { row-gap: 2em; } @@ -495,6 +471,9 @@ .py-\[0\.5em\] { padding-block: 0.5em; } + .py-\[2em\] { + padding-block: 2em; + } .pt-\[1em\] { padding-top: 1em; } @@ -545,9 +524,6 @@ .italic { font-style: italic; } - .no-underline { - text-decoration-line: none; - } .underline { text-decoration-line: underline; } @@ -652,48 +628,11 @@ } } } - .group-open\/create-mock-shop\:after\:content-\[\'-\'\] { - &:is(:where(.group\/create-mock-shop):is([open], :popover-open, :open) *) { - &::after { - --tw-content: '-'; - content: var(--tw-content); - } - } - } - .group-open\/link-shops\:after\:content-\[\'-\'\] { - &:is(:where(.group\/link-shops):is([open], :popover-open, :open) *) { - &::after { - --tw-content: '-'; - content: var(--tw-content); - } - } - } - .open\:mb-\[1em\] { - &:is([open], :popover-open, :open) { - margin-bottom: 1em; - } - } .open\:grid { &:is([open], :popover-open, :open) { display: grid; } } - .open\:border-b-\[1px\] { - &:is([open], :popover-open, :open) { - border-bottom-style: var(--tw-border-style); - border-bottom-width: 1px; - } - } - .open\:border-b-background { - &:is([open], :popover-open, :open) { - border-bottom-color: var(--background); - } - } - .open\:pb-\[1em\] { - &:is([open], :popover-open, :open) { - padding-bottom: 1em; - } - } .open\:details-content\:transform-\[translate\(0px\,0px\)\] { &:is([open], :popover-open, :open) { &::details-content { @@ -717,14 +656,6 @@ } } } - .open\:details-content\:border-t-\[0\.225rem\] { - &:is([open], :popover-open, :open) { - &::details-content { - border-top-style: var(--tw-border-style); - border-top-width: 0.225rem; - } - } - } .open\:details-content\:border-b-\[0px\] { &:is([open], :popover-open, :open) { &::details-content { @@ -776,13 +707,6 @@ } } } - .hover\:no-underline { - &:hover { - @media (hover: hover) { - text-decoration-line: none; - } - } - } .hover\:underline { &:hover { @media (hover: hover) { diff --git a/templates/components/accounts/{acctID.int64}/inventory/sync-groups/mock/draft/listings/{orderIndex.int}.html.tmpl b/templates/components/accounts/{acctID.int64}/inventory/sync-groups/mock/draft/listings/{orderIndex.int}.html.tmpl new file mode 100644 index 0000000..e76454e --- /dev/null +++ b/templates/components/accounts/{acctID.int64}/inventory/sync-groups/mock/draft/listings/{orderIndex.int}.html.tmpl @@ -0,0 +1,140 @@ +{{/* optional props: .Listings = .Accounts.GetMockSyncGroupListingDrafts */}} + +{{- $acctID := .PathParams.acctID }} +{{- $orderIndex := .PathParams.orderIndex }} +{{- $entry := .Accounts.GetMockSyncGroupListingDraft $acctID $orderIndex }} +{{- $selectedShopPlatform := $entry.Platform }} +{{- $selectedShopID := $entry.ShopID }} +{{- $selectedListingID := $entry.ListingID }} + +{{/* TODO: is this supposed to be the mock listings (not draft listings)? */}} +{{- $listings := .Listings }} +{{- if not $listings }} + {{- $listings = .Accounts.GetMockSyncGroupListingDrafts $acctID }} +{{- end }} + + +
| + Shop + | ++ Listing + | ++ SKU + | ++ Description + | ++ + | +
|---|---|---|---|---|
| + Shop + | ++ Listing + | ++ SKU + | ++ Description + | ++ + | +