diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image003.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image003.jpg deleted file mode 100644 index cc60b5a03..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image003.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image004.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image004.jpg deleted file mode 100644 index ed082700e..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image004.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image007.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image007.jpg deleted file mode 100644 index 4e4df9e51..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image007.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image008.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image008.jpg deleted file mode 100644 index fce400f01..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image008.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image010.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image010.jpg deleted file mode 100644 index e7afad3d8..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image010.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image011.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image011.jpg deleted file mode 100644 index 79d9972be..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image011.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image012.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image012.jpg deleted file mode 100644 index bdac9d1dd..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image012.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image014.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image014.png deleted file mode 100644 index b8f08a160..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image014.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image015.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image015.jpg deleted file mode 100644 index d3a742456..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image015.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image016.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image016.jpg deleted file mode 100644 index cad9614fb..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image016.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image017.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image017.jpg deleted file mode 100644 index 7d9674c3f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image017.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image018.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image018.jpg deleted file mode 100644 index 25ce0f073..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image018.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image019.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image019.jpg deleted file mode 100644 index 02c9218ce..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image019.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image020.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image020.jpg deleted file mode 100644 index 0c617273f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image020.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image021.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image021.jpg deleted file mode 100644 index 3ff9132d5..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image021.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image022.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image022.jpg deleted file mode 100644 index 9bd4e5b28..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image022.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image023.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image023.jpg deleted file mode 100644 index f9e919262..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image023.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image024.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image024.jpg deleted file mode 100644 index 7d37a3131..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image024.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image025.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image025.jpg deleted file mode 100644 index 5c4d8db0e..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image025.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image026.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image026.jpg deleted file mode 100644 index 0e71d1590..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image026.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image027.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image027.png deleted file mode 100644 index 514e3a331..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image027.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image029.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image029.jpg deleted file mode 100644 index 066466487..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image029.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image031.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image031.jpg deleted file mode 100644 index 10ecc77f7..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image031.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image032.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image032.jpg deleted file mode 100644 index db4289c4d..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image032.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image034.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image034.jpg deleted file mode 100644 index 46c7f96b0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image034.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image036.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image036.jpg deleted file mode 100644 index dad063975..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image036.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image037.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image037.jpg deleted file mode 100644 index e0c58f3bb..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image037.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image038.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image038.jpg deleted file mode 100644 index a5a3661cc..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image038.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image039.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image039.jpg deleted file mode 100644 index 2823495e7..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image039.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image040.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image040.jpg deleted file mode 100644 index d12c88523..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image040.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image041.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image041.jpg deleted file mode 100644 index 6d96604df..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image041.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image043.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image043.jpg deleted file mode 100644 index 7d93e1649..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image043.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image045.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image045.jpg deleted file mode 100644 index 3cce49c65..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image045.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image046.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image046.jpg deleted file mode 100644 index 6193991df..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image046.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image047.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image047.jpg deleted file mode 100644 index 89606154e..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image047.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image048.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image048.jpg deleted file mode 100644 index 29344faaa..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image048.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image049.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image049.jpg deleted file mode 100644 index 94b9500fb..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image049.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image050.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image050.jpg deleted file mode 100644 index deb825a99..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image050.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image051.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image051.jpg deleted file mode 100644 index 11f5e2d89..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image051.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image052.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image052.jpg deleted file mode 100644 index b0281ae12..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image052.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image053.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image053.jpg deleted file mode 100644 index f7454350d..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image053.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image054.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image054.jpg deleted file mode 100644 index f2ddeb160..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image054.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image055.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image055.jpg deleted file mode 100644 index f8f24c6d1..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image055.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image056.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image056.jpg deleted file mode 100644 index 9c341f1fd..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image056.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image057.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image057.jpg deleted file mode 100644 index abf3fc68d..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image057.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image058.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image058.jpg deleted file mode 100644 index 8a0666dd6..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image058.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image059.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image059.jpg deleted file mode 100644 index 91d759bf0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image059.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image060.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image060.jpg deleted file mode 100644 index 933993d72..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image060.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image061.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image061.jpg deleted file mode 100644 index bc6000976..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image061.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image062.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image062.jpg deleted file mode 100644 index 5bff09c18..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image062.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image063.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image063.jpg deleted file mode 100644 index bfe600501..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image063.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image064.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image064.jpg deleted file mode 100644 index 649fac4f1..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image064.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image065.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image065.jpg deleted file mode 100644 index 682f2753b..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image065.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image066.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image066.jpg deleted file mode 100644 index f8b9b8bdc..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image066.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image067.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image067.jpg deleted file mode 100644 index 1eee93405..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image067.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image068.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image068.jpg deleted file mode 100644 index 04c7d92c8..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image068.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image069.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image069.jpg deleted file mode 100644 index 1ea921287..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image069.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image070.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image070.jpg deleted file mode 100644 index 564daaf15..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image070.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image071.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image071.jpg deleted file mode 100644 index 9e6c669bb..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image071.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image072.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image072.jpg deleted file mode 100644 index 014283899..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image072.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image073.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image073.jpg deleted file mode 100644 index d91a050d0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image073.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image074.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image074.jpg deleted file mode 100644 index 972c2545a..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image074.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image075.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image075.jpg deleted file mode 100644 index 265aa3eaa..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image075.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image076.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image076.jpg deleted file mode 100644 index 64a9f80c0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image076.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image077.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image077.jpg deleted file mode 100644 index 8ef89b83f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image077.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image078.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image078.jpg deleted file mode 100644 index e0715f016..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image078.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image079.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image079.jpg deleted file mode 100644 index 0a39d4294..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image079.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image080.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image080.jpg deleted file mode 100644 index 987174408..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image080.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image081.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image081.jpg deleted file mode 100644 index d2544eeb8..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image081.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image082.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image082.jpg deleted file mode 100644 index 030e34294..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image082.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image083.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image083.jpg deleted file mode 100644 index 18b82e2e8..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image083.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image084.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image084.jpg deleted file mode 100644 index 699223261..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image084.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image085.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image085.jpg deleted file mode 100644 index 76bc42352..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image085.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image086.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image086.jpg deleted file mode 100644 index 044379b09..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image086.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image087.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image087.jpg deleted file mode 100644 index 2af48297f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image087.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image088.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image088.jpg deleted file mode 100644 index fd6e01f7a..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image088.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image089.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image089.jpg deleted file mode 100644 index 0878241d6..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image089.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image090.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image090.jpg deleted file mode 100644 index beffb704f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image090.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image091.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image091.jpg deleted file mode 100644 index fdb59362e..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image091.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image092.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image092.jpg deleted file mode 100644 index 4310fe118..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image092.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image093.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image093.jpg deleted file mode 100644 index 308180515..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image093.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image094.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image094.jpg deleted file mode 100644 index 86267c06d..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image094.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image095.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image095.jpg deleted file mode 100644 index 7d998de41..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image095.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image096.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image096.jpg deleted file mode 100644 index e8b620277..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image096.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image097.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image097.jpg deleted file mode 100644 index 6b08635b8..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image097.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image098.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image098.jpg deleted file mode 100644 index 45b250d0e..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image098.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image099.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image099.jpg deleted file mode 100644 index 7acff2777..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image099.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image100.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image100.jpg deleted file mode 100644 index d18ac956e..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image100.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image101.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image101.jpg deleted file mode 100644 index e380166bd..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image101.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image102.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image102.jpg deleted file mode 100644 index 7663d202f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image102.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image103.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image103.jpg deleted file mode 100644 index e1d8d986f..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image103.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image104.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image104.jpg deleted file mode 100644 index 04e64609d..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image104.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image105.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image105.jpg deleted file mode 100644 index ffd8abc85..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image105.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image106.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image106.jpg deleted file mode 100644 index 473e5d5f6..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image106.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image107.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image107.jpg deleted file mode 100644 index fca57e4a2..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image107.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image108.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image108.jpg deleted file mode 100644 index 034adaae3..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image108.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image109.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image109.jpg deleted file mode 100644 index 321256fd0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image109.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image110.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image110.jpg deleted file mode 100644 index 548be4507..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image110.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image111.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image111.jpg deleted file mode 100644 index ad0648646..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image111.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image112.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image112.jpg deleted file mode 100644 index a65103ce7..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image112.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image113.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image113.jpg deleted file mode 100644 index b36dfd5c9..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image113.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image114.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image114.jpg deleted file mode 100644 index fa3487a67..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image114.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image115.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image115.jpg deleted file mode 100644 index 29106104c..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image115.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image116.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image116.jpg deleted file mode 100644 index fdd016c95..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image116.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image117.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image117.jpg deleted file mode 100644 index c95ef4f8c..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image117.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image118.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image118.jpg deleted file mode 100644 index f7e4e0672..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image118.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image119.jpg b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image119.jpg deleted file mode 100644 index 78f2c4ef0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image119.jpg and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image120.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image120.png deleted file mode 100644 index 19a0f6e30..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image120.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image121.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image121.png deleted file mode 100644 index eaab2ff53..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image121.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image122.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image122.png deleted file mode 100644 index 68cbdb393..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image122.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image123.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image123.png deleted file mode 100644 index a03bfc2ba..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image123.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image124.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image124.png deleted file mode 100644 index 88399d2a6..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image124.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image125.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image125.png deleted file mode 100644 index 96fc2ab90..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image125.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image126.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image126.png deleted file mode 100644 index 4a5eebf87..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image126.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image127.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image127.png deleted file mode 100644 index f701e2b75..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image127.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image128.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image128.png deleted file mode 100644 index d16f2acca..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image128.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image129.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image129.png deleted file mode 100644 index 5d7f8689b..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image129.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image130.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image130.png deleted file mode 100644 index 6bc803902..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image130.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image131.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image131.png deleted file mode 100644 index d5b55bda0..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image131.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image132.png b/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image132.png deleted file mode 100644 index e88ed7a34..000000000 Binary files a/03-Azure/01-02 Data/03-Talk_to_your_data/Images/image132.png and /dev/null differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/README.md b/03-Azure/01-02 Data/03-Talk_to_your_data/README.md index ad96a9cb1..da1a060ad 100644 --- a/03-Azure/01-02 Data/03-Talk_to_your_data/README.md +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/README.md @@ -1,4 +1,4 @@ -![image](./Images/Preview.png) +![image](./images/Preview.png) # Ask Analyze Act - Talk to Your Data in the Era of AI diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/README.md b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/README.md new file mode 100644 index 000000000..cc2f8ea07 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/README.md @@ -0,0 +1,72 @@ +# TTYD lab automation + +MicroHack [labautomation](https://github.com/microsoft/MicroHack/blob/main/99-MicroHack-Template/labautomation/README.md) +integration for the Talk-To-Your-Data MicroHack. + +## Layout + +| File | Purpose | +| --- | --- | +| `lab-defaults.json` | Platform sizing: `resourcegroup`, 42 labs/subscription. | +| `shared-deploy-lab.ps1` | Runs once per subscription → `rg-shared`. Shared VNet, SQL MI, Fabric F32 capacity + gateway, backup storage, shared webshop, demo databases, stored proc, product, Agent job. | +| `deploy-lab.ps1` | Runs once per attendee. Two databases in the shared MI, the attendee's login + Fabric workspace, a per-attendee CSV storage account. | +| `shared.bicep` / `main.bicep` | ARM templates for the shared stack and the per-attendee resources (reuse `infra/modules`). | +| `sql/` | `StoredProcedure.sql`, `Jobs.sql`, `InsertProduct.sql` (discover per-attendee DBs by name pattern). | + +## Split model + +- **Shared, once per subscription:** one SQL Managed Instance and one Fabric F32 + capacity. The webshop and the `usp_PurchaseSpaceRanger` proc / Agent job are shared + and fan out to every attendee database that exists. +- **Per attendee:** `TailspinToys_` + `TailspinToysFeedback_` in the + shared MI, one Fabric workspace on the shared capacity, one CSV storage account in + the attendee resource group. + +## Prerequisites + +- **Fabric APIs for service principals** must be enabled in the tenant admin portal + (the platform runs as a service principal). Without it, gateway/workspace creation + fails. +- SQL is accessed with an Entra token as the deploying principal, which + `shared-deploy-lab.ps1` sets as the SQL MI Entra admin (plus Directory Readers on the + MI identity). No shared SQL password is distributed. +- The scripts read `databasebackup/*.bak` and `csvdata/*.csv` and reuse + `infra/modules`, all local to this folder, so **mount this `labautomation` folder** for local testing. + +## Local testing + +Run the [adminpwsh](https://github.com/qxsch/adminpwsh) container from the +**`labautomation` folder** so the scripts can reach `databasebackup`, `csvdata` and `infra`: + +```powershell +docker run -it -v "${PWD}:/app" --rm "ghcr.io/qxsch/adminpwsh:latest" +``` + +Inside the container, sign in yourself (the platform does this automatically in +production — do not add these logins to the scripts): + +```powershell +Connect-AzAccount -UseDeviceAuthentication +az login --use-device-code +cd /app +``` + +Then run the two hooks in the real order. Pass more than one object id to +`-AllowedEntraUserIds` to check RBAC covers every attendee: + +```powershell +./shared-deploy-lab.ps1 ` + -SubscriptionId (Get-AzContext).Subscription.Id ` + -PreferredLocation "swedencentral" ` + -AllowedEntraUserIds (Get-AzADUser -SignedIn).Id + +./deploy-lab.ps1 ` + -DeploymentType resourcegroup ` + -SubscriptionId (Get-AzContext).Subscription.Id ` + -ResourceGroupName "rg-lab-local-test" ` + -PreferredLocation "swedencentral" ` + -AllowedEntraUserIds (Get-AzADUser -SignedIn).Id +``` + +> Provisioning a SQL Managed Instance takes hours; `shared-deploy-lab.ps1` submits it +> asynchronously and refreshes credentials while it polls. diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/csvdata/.gitignore b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/csvdata/.gitignore new file mode 100644 index 000000000..28c9142fa --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/csvdata/.gitignore @@ -0,0 +1,12 @@ +# CSV seed / sample data + +# Tracked: the *.csv data files used to seed the demo. + +# Ignore editor lock files and temporary exports: +~$* +*.tmp +*.bak + +# Ignore spreadsheet copies that may be created while editing the data +*.xlsx +*.xls diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/csvdata/employees_user_data.csv b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/csvdata/employees_user_data.csv new file mode 100644 index 000000000..17af44f2c --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/csvdata/employees_user_data.csv @@ -0,0 +1,154 @@ +EmployeeID,FirstName,LastName,Email,Title,Department,HireDate,Salary,SalesOfficeID +1,Levi,Kelly,levi.kelly@tailspintoys.com,HR Generalist,IT,2014-10-16,90000,3 +2,Ella,Edwards,ella.edwards@tailspintoys.com,Sales Associate,HR,2023-10-17,63000,2 +3,Andrew,Wright,andrew.wright@tailspintoys.com,Customer Success Manager,Customer Success,2018-10-14,75000,1 +4,Anthony,Carter,anthony.carter@tailspintoys.com,Marketing Specialist,Customer Success,2019-11-02,72000,2 +5,Hannah,King,hannah.king@tailspintoys.com,Customer Success Manager,IT,2019-06-25,78000,1 +6,Jack,Clark,jack.clark@tailspintoys.com,Account Executive,IT,2022-03-26,113000,9 +7,Camila,Collins,camila.collins@tailspintoys.com,IT Support,HR,2024-11-16,90000,10 +8,Leah,Peterson,leah.peterson@tailspintoys.com,HR Generalist,HR,2017-08-31,87000,3 +9,Leah,Lopez,leah.lopez@tailspintoys.com,IT Support,Marketing,2023-12-04,58000,5 +10,Mason,King,mason.king@tailspintoys.com,HR Generalist,Operations,2025-11-04,84000,2 +11,Aurora,Gray,aurora.gray@tailspintoys.com,Sales Manager,Finance,2015-11-29,82000,2 +12,Hazel,Rodriguez,hazel.rodriguez@tailspintoys.com,Marketing Specialist,Customer Success,2014-10-09,79000,1 +13,Avery,Baker,avery.baker@tailspintoys.com,Marketing Specialist,Customer Success,2018-12-28,90000,2 +14,Anthony,Moore,anthony.moore@tailspintoys.com,Sales Associate,IT,2014-07-26,75000,10 +15,Lincoln,Murphy,lincoln.murphy@tailspintoys.com,IT Support,HR,2015-09-21,89000,10 +16,Brooklyn,Sanchez,brooklyn.sanchez@tailspintoys.com,Sales Manager,IT,2024-05-10,102000,8 +17,Amelia,Brown,amelia.brown@tailspintoys.com,IT Support,Sales,2016-12-21,55000,4 +18,Charlotte,Reyes,charlotte.reyes@tailspintoys.com,Sales Manager,Operations,2013-09-20,73000,7 +19,Matthew,Collins,matthew.collins@tailspintoys.com,HR Generalist,IT,2016-04-07,98000,9 +20,Emily,Garcia,emily.garcia@tailspintoys.com,Sales Manager,HR,2014-05-04,54000,8 +21,Angel,Nguyen,angel.nguyen@tailspintoys.com,HR Generalist,IT,2022-04-03,93000,2 +22,Hannah,Stewart,hannah.stewart@tailspintoys.com,Marketing Specialist,Finance,2017-05-24,71000,7 +23,Abigail,Parker,abigail.parker@tailspintoys.com,Account Executive,Legal,2019-11-07,83000,4 +24,Camila,Clark,camila.clark@tailspintoys.com,Account Executive,Customer Success,2016-09-12,56000,3 +25,Gabriel,Mendoza,gabriel.mendoza@tailspintoys.com,Legal Counsel,Marketing,2022-04-06,82000,4 +26,Grayson,Mitchell,grayson.mitchell@tailspintoys.com,Legal Counsel,Operations,2024-05-04,75000,1 +27,David,Diaz,david.diaz@tailspintoys.com,Marketing Specialist,HR,2021-11-20,66000,9 +28,Andrew,Watson,andrew.watson@tailspintoys.com,Sales Associate,Operations,2017-02-16,66000,5 +29,Aubrey,Jenkins,aubrey.jenkins@tailspintoys.com,IT Support,Operations,2021-08-18,94000,7 +30,John,Price,john.price@tailspintoys.com,Account Executive,IT,2019-09-24,77000,9 +31,David,Hernandez,david.hernandez@tailspintoys.com,HR Generalist,Customer Success,2016-03-13,79000,2 +32,Daniel,Nelson,daniel.nelson@tailspintoys.com,Customer Success Manager,IT,2015-12-14,68000,8 +33,Grayson,Green,grayson.green@tailspintoys.com,Marketing Specialist,Marketing,2020-04-07,99000,6 +34,John,Alvarez,john.alvarez@tailspintoys.com,HR Generalist,HR,2017-09-07,56000,5 +35,Camila,Green,camila.green@tailspintoys.com,Sales Associate,HR,2025-08-25,53000,8 +36,Leah,Nelson,leah.nelson@tailspintoys.com,Account Executive,Marketing,2021-06-20,95000,3 +37,Julian,Walker,julian.walker@tailspintoys.com,Legal Counsel,Customer Success,2020-05-20,80000,3 +38,Hazel,Cox,hazel.cox@tailspintoys.com,Legal Counsel,Sales,2020-05-09,107000,6 +39,Levi,Chavez,levi.chavez@tailspintoys.com,Marketing Specialist,HR,2017-12-16,65000,3 +40,Anna,Ramos,anna.ramos@tailspintoys.com,Marketing Specialist,HR,2022-01-26,75000,8 +41,Gabriel,Hernandez,gabriel.hernandez@tailspintoys.com,Customer Success Manager,Sales,2016-09-23,88000,1 +42,Angel,Thomas,angel.thomas@tailspintoys.com,Operations Coordinator,Operations,2018-07-13,66000,8 +43,Natalie,Jackson,natalie.jackson@tailspintoys.com,Sales Associate,Marketing,2012-06-02,83000,10 +44,Penelope,Cook,penelope.cook@tailspintoys.com,Customer Success Manager,Marketing,2017-06-26,99000,3 +45,Zoe,Gonzalez,zoe.gonzalez@tailspintoys.com,Legal Counsel,IT,2020-08-08,83000,7 +46,Jack,Cox,jack.cox@tailspintoys.com,IT Support,Marketing,2023-06-21,77000,6 +47,Ezra,Mendoza,ezra.mendoza@tailspintoys.com,HR Generalist,Sales,2012-07-04,74000,9 +48,Isabella,Ward,isabella.ward@tailspintoys.com,IT Support,Customer Success,2025-06-04,65000,10 +49,Jack,Thomas,jack.thomas@tailspintoys.com,Account Executive,Operations,2013-05-04,78000,8 +50,Aubrey,Allen,aubrey.allen@tailspintoys.com,Finance Analyst,Legal,2024-07-13,94000,9 +51,Carter,Turner,carter.turner@tailspintoys.com,Marketing Specialist,Operations,2019-02-23,58000,3 +52,Noah,Evans,noah.evans@tailspintoys.com,Sales Manager,Finance,2018-03-08,77000,2 +53,Dylan,Campbell,dylan.campbell@tailspintoys.com,IT Support,Sales,2022-07-25,55000,6 +54,Alexander,Cruz,alexander.cruz@tailspintoys.com,IT Support,Marketing,2023-03-28,66000,1 +55,Ella,Sanchez,ella.sanchez@tailspintoys.com,Marketing Specialist,Legal,2017-03-18,137000,3 +56,Charlotte,Hill,charlotte.hill@tailspintoys.com,HR Generalist,Marketing,2013-07-29,95000,6 +57,Wyatt,Kelly,wyatt.kelly@tailspintoys.com,Sales Associate,HR,2015-10-19,81000,3 +58,Lillian,Hernandez,lillian.hernandez@tailspintoys.com,IT Support,HR,2015-05-12,86000,4 +59,Carter,Ross,carter.ross@tailspintoys.com,Legal Counsel,Finance,2022-03-20,92000,5 +60,Carter,Martinez,carter.martinez@tailspintoys.com,Account Executive,Legal,2019-08-04,127000,8 +61,Wyatt,Reed,wyatt.reed@tailspintoys.com,IT Support,Operations,2020-09-12,64000,3 +62,Stella,Wilson,stella.wilson@tailspintoys.com,Legal Counsel,Operations,2016-12-16,81000,2 +63,Gabriel,Jackson,gabriel.jackson@tailspintoys.com,Customer Success Manager,HR,2014-12-23,51000,7 +64,Michael,Stewart,michael.stewart@tailspintoys.com,Marketing Specialist,Marketing,2024-02-05,82000,4 +65,Lincoln,Phillips,lincoln.phillips@tailspintoys.com,IT Support,Sales,2021-02-08,58000,3 +66,Samuel,Walker,samuel.walker@tailspintoys.com,Customer Success Manager,Customer Success,2025-08-31,88000,10 +67,Savannah,Clark,savannah.clark@tailspintoys.com,Legal Counsel,Sales,2017-05-03,82000,1 +68,Noah,Bennett,noah.bennett@tailspintoys.com,Finance Analyst,Customer Success,2017-08-20,69000,2 +69,Ava,Powell,ava.powell@tailspintoys.com,IT Support,IT,2024-02-21,103000,1 +70,Lucas,Lopez,lucas.lopez@tailspintoys.com,Sales Associate,Sales,2012-04-12,71000,9 +71,Amelia,Morris,amelia.morris@tailspintoys.com,Finance Analyst,HR,2024-09-09,52000,5 +72,Liam,Gonzalez,liam.gonzalez@tailspintoys.com,Sales Associate,Sales,2015-01-26,120000,9 +73,Harper,Nelson,harper.nelson@tailspintoys.com,Sales Associate,HR,2016-11-06,64000,8 +74,Nora,Peterson,nora.peterson@tailspintoys.com,IT Support,Finance,2013-03-06,86000,4 +75,Paisley,Jimenez,paisley.jimenez@tailspintoys.com,Account Executive,Marketing,2017-08-01,53000,7 +76,Jackson,Price,jackson.price@tailspintoys.com,Marketing Specialist,Customer Success,2012-08-28,94000,6 +77,Violet,Cruz,violet.cruz@tailspintoys.com,Sales Manager,IT,2023-07-05,67000,9 +78,Jayden,Miller,jayden.miller@tailspintoys.com,Legal Counsel,Marketing,2024-06-08,51000,9 +79,Noah,Jimenez,noah.jimenez@tailspintoys.com,IT Support,Sales,2021-02-25,100000,5 +80,Christopher,Reed,christopher.reed@tailspintoys.com,Operations Coordinator,Legal,2024-02-01,94000,1 +81,Ryan,Alvarez,ryan.alvarez@tailspintoys.com,IT Support,HR,2015-07-22,61000,10 +82,Angel,Moore,angel.moore@tailspintoys.com,Customer Success Manager,Finance,2012-03-12,79000,5 +83,Isaac,Long,isaac.long@tailspintoys.com,Sales Manager,Operations,2016-07-22,65000,9 +84,Natalie,Hall,natalie.hall@tailspintoys.com,Sales Manager,Operations,2014-03-17,61000,1 +85,Isabella,Roberts,isabella.roberts@tailspintoys.com,Sales Associate,Marketing,2015-08-21,65000,2 +86,Stella,Reed,stella.reed@tailspintoys.com,Account Executive,IT,2017-10-14,103000,8 +87,Harper,Allen,harper.allen@tailspintoys.com,HR Generalist,Customer Success,2017-09-03,76000,7 +88,Anthony,Davis,anthony.davis@tailspintoys.com,Sales Associate,Marketing,2022-09-04,58000,7 +89,Grayson,Garcia,grayson.garcia@tailspintoys.com,Legal Counsel,Legal,2024-10-31,97000,7 +90,Anthony,Richardson,anthony.richardson@tailspintoys.com,HR Generalist,HR,2023-04-16,79000,3 +91,Camila,Jenkins,camila.jenkins@tailspintoys.com,Operations Coordinator,Customer Success,2014-04-30,85000,6 +92,Brooklyn,Mitchell,brooklyn.mitchell@tailspintoys.com,Customer Success Manager,Customer Success,2020-10-29,72000,10 +93,John,Torres,john.torres@tailspintoys.com,Customer Success Manager,HR,2018-11-14,89000,10 +94,Dylan,Jackson,dylan.jackson@tailspintoys.com,Sales Manager,Marketing,2020-08-15,73000,1 +95,Savannah,Carter,savannah.carter@tailspintoys.com,Sales Manager,Legal,2016-08-27,118000,3 +96,Abigail,Mendoza,abigail.mendoza@tailspintoys.com,HR Generalist,Finance,2018-12-29,88000,4 +97,Jackson,Mitchell,jackson.mitchell@tailspintoys.com,Operations Coordinator,Sales,2019-08-12,106000,8 +98,James,Castillo,james.castillo@tailspintoys.com,Finance Analyst,IT,2014-09-09,64000,10 +99,Carter,Morris,carter.morris@tailspintoys.com,Operations Coordinator,Operations,2013-04-01,53000,9 +100,Lily,Ward,lily.ward@tailspintoys.com,Legal Counsel,Legal,2018-04-14,130000,10 +101,Leah,King,leah.king@tailspintoys.com,Operations Coordinator,IT,2014-12-11,105000,3 +102,Amelia,Foster,amelia.foster@tailspintoys.com,Marketing Specialist,Marketing,2014-05-19,88000,3 +103,Henry,Jackson,henry.jackson@tailspintoys.com,Finance Analyst,Legal,2021-12-21,129000,4 +104,Ryan,Ross,ryan.ross@tailspintoys.com,IT Support,IT,2025-12-21,84000,6 +105,Scarlett,Ortiz,scarlett.ortiz@tailspintoys.com,Legal Counsel,IT,2016-08-06,96000,7 +106,Hazel,Carter,hazel.carter@tailspintoys.com,Operations Coordinator,Sales,2015-06-04,92000,8 +107,Caleb,Gomez,caleb.gomez@tailspintoys.com,Sales Associate,IT,2020-09-19,71000,8 +108,Luke,Johnson,luke.johnson@tailspintoys.com,Finance Analyst,Operations,2021-06-15,74000,4 +109,Ryan,Parker,ryan.parker@tailspintoys.com,Sales Manager,Customer Success,2021-09-16,56000,5 +110,Penelope,Bailey,penelope.bailey@tailspintoys.com,Operations Coordinator,Sales,2019-03-08,73000,8 +111,Amelia,Chavez,amelia.chavez@tailspintoys.com,Legal Counsel,Operations,2022-05-23,59000,6 +112,Aurora,Edwards,aurora.edwards@tailspintoys.com,Marketing Specialist,HR,2023-12-20,52000,9 +113,Lucy,Cooper,lucy.cooper@tailspintoys.com,Marketing Specialist,IT,2012-11-26,108000,2 +114,Abigail,Johnson,abigail.johnson@tailspintoys.com,Account Executive,IT,2017-07-12,103000,6 +115,Joshua,Davis,joshua.davis@tailspintoys.com,Finance Analyst,Legal,2025-06-11,110000,2 +116,Avery,Brooks,avery.brooks@tailspintoys.com,Sales Manager,Customer Success,2019-06-13,75000,3 +117,Josiah,Martinez,josiah.martinez@tailspintoys.com,Finance Analyst,Sales,2012-09-23,113000,7 +118,Oliver,Sanders,oliver.sanders@tailspintoys.com,Legal Counsel,Customer Success,2015-07-11,67000,7 +119,Jacob,Reed,jacob.reed@tailspintoys.com,Customer Success Manager,Customer Success,2012-05-02,84000,5 +120,Olivia,Wright,olivia.wright@tailspintoys.com,Customer Success Manager,Operations,2012-08-08,88000,9 +121,Charlotte,Harris,charlotte.harris@tailspintoys.com,Sales Manager,HR,2024-05-21,85000,6 +122,Anna,Anderson,anna.anderson@tailspintoys.com,Customer Success Manager,IT,2012-09-08,66000,2 +123,Sophia,Sanchez,sophia.sanchez@tailspintoys.com,Sales Associate,HR,2020-05-15,84000,2 +124,Harper,Clark,harper.clark@tailspintoys.com,Sales Associate,Finance,2013-08-29,102000,4 +125,Harper,Bailey,harper.bailey@tailspintoys.com,IT Support,Customer Success,2020-03-03,70000,7 +126,Christian,King,christian.king@tailspintoys.com,Customer Success Manager,Customer Success,2014-03-29,87000,5 +127,Jacob,Wilson,jacob.wilson@tailspintoys.com,Customer Success Manager,Operations,2021-10-10,89000,7 +128,Eleanor,Baker,eleanor.baker@tailspintoys.com,Sales Associate,Marketing,2020-06-03,88000,8 +129,Violet,Ward,violet.ward@tailspintoys.com,Sales Associate,Marketing,2021-04-20,86000,7 +130,James,Rogers,james.rogers@tailspintoys.com,Sales Associate,Sales,2016-05-09,99000,3 +131,Scarlett,Murphy,scarlett.murphy@tailspintoys.com,Legal Counsel,Legal,2016-04-12,105000,7 +132,Logan,Jenkins,logan.jenkins@tailspintoys.com,Sales Associate,Finance,2022-01-28,67000,7 +133,Lillian,Lee,lillian.lee@tailspintoys.com,Finance Analyst,Marketing,2014-04-14,82000,7 +134,Jayden,Powell,jayden.powell@tailspintoys.com,Sales Manager,Sales,2012-10-26,64000,10 +135,James,Davis,james.davis@tailspintoys.com,Sales Associate,IT,2013-01-17,57000,10 +136,Ezra,Garcia,ezra.garcia@tailspintoys.com,HR Generalist,Legal,2020-01-06,83000,2 +137,Audrey,Brown,audrey.brown@tailspintoys.com,Operations Coordinator,Finance,2021-04-10,104000,10 +138,Mason,Martin,mason.martin@tailspintoys.com,Marketing Specialist,Sales,2015-10-03,93000,4 +139,Carter,Gonzalez,carter.gonzalez@tailspintoys.com,Sales Associate,Marketing,2015-06-15,73000,5 +140,Leah,Long,leah.long@tailspintoys.com,Finance Analyst,HR,2024-12-09,81000,1 +141,Claire,Campbell,claire.campbell@tailspintoys.com,HR Generalist,Finance,2018-02-22,104000,2 +142,Christopher,Williams,christopher.williams@tailspintoys.com,Account Executive,HR,2017-07-08,87000,7 +143,Zoey,Powell,zoey.powell@tailspintoys.com,Customer Success Manager,Finance,2022-06-02,77000,5 +144,Noah,Mitchell,noah.mitchell@tailspintoys.com,Sales Manager,Sales,2014-06-11,117000,8 +145,Jackson,Williams,jackson.williams@tailspintoys.com,IT Support,Marketing,2022-03-12,94000,1 +146,Jack,Rivera,jack.rivera@tailspintoys.com,IT Support,Finance,2019-08-23,62000,4 +147,Luke,Johnson,luke.johnson+147@tailspintoys.com,Operations Coordinator,Operations,2025-05-16,72000,2 +148,Audrey,Lee,audrey.lee@tailspintoys.com,IT Support,Marketing,2018-04-09,59000,9 +149,Bella,Garcia,bella.garcia@tailspintoys.com,Customer Success Manager,Customer Success,2020-01-18,87000,5 +150,Emma,Murphy,emma.murphy@tailspintoys.com,Marketing Specialist,Finance,2023-05-22,88000,2 +151,Joshua,Reed,joshua.reed@tailspintoys.com,Sales Manager,Legal,2016-11-05,86000,10 +152,Oliver,Cook,oliver.cook@tailspintoys.com,Sales Associate,HR,2015-08-14,71000,4 +153,Benjamin,Torres,benjamin.torres@tailspintoys.com,Finance Analyst,Finance,2019-11-26,87000,5 diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/.gitignore b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/.gitignore new file mode 100644 index 000000000..c9c74d706 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/.gitignore @@ -0,0 +1,10 @@ +# Database backups + +# Tracked: the *.bak restore files required by the microhack. + +# Ignore other backup / temporary artifacts: +*.trn # transaction log backups +*.zip # compressed backups +*.7z +*.gz +*.tmp diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/tailspintoys_before_launch.bak b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/tailspintoys_before_launch.bak new file mode 100644 index 000000000..3d453871d Binary files /dev/null and b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/tailspintoys_before_launch.bak differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/tailspintoysfeedback_before_launch.bak b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/tailspintoysfeedback_before_launch.bak new file mode 100644 index 000000000..9b2c12a49 Binary files /dev/null and b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/databasebackup/tailspintoysfeedback_before_launch.bak differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/deploy-lab.ps1 b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/deploy-lab.ps1 new file mode 100644 index 000000000..ee547bbb8 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/deploy-lab.ps1 @@ -0,0 +1,224 @@ +<# +.SYNOPSIS +Deploys the lab resources scoped to a subscription or resource group. +.DESCRIPTION +Provides a controlled deployment flow for lab environments, optionally limited to a resource group and specific Entra user IDs. +.PARAMETER DeploymentType +Defines the deployment scope; allowed values are subscription or resourcegroup. +.PARAMETER SubscriptionId +Specifies the Azure subscription that contains the lab resources. +.PARAMETER ResourceGroupName +In case of resourcegroup deployment, specifies the target resource group name. +.PARAMETER PreferredLocation +Specifies the preferred Azure regions (ordered by preference) for resource deployment. An empty array indicates no preference. +.PARAMETER AllowedEntraUserIds +Optional list of Entra user object IDs permitted to access the lab resources. +#> +param( + [Parameter(Mandatory=$true)] + [ValidateSet('subscription','resourcegroup', 'resourcegroup-with-subscriptionowner')] + [string]$DeploymentType, + + [Parameter(Mandatory=$true)] + [string]$SubscriptionId, + + [string]$ResourceGroupName = "", + + [string[]]$PreferredLocation = @(), + + [string[]]$AllowedEntraUserIds = @() +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$SharedResourceGroup = 'rg-shared' +$FabricApi = 'https://api.fabric.microsoft.com/v1' +$TailspinToysBak = 'tailspintoys_before_launch.bak' +$TailspinToysFeedbackBak = 'tailspintoysfeedback_before_launch.bak' + +# ───────────────────────────────────────────── +# Helpers +# ───────────────────────────────────────────── +if (-not (Get-Module -ListAvailable -Name SqlServer)) { + Install-Module -Name SqlServer -Scope CurrentUser -Repository PSGallery -Force -AllowClobber +} +Import-Module SqlServer -ErrorAction Stop + +function Get-DbAccessToken { + $t = (Get-AzAccessToken -ResourceUrl 'https://database.windows.net/').Token + if ($t -is [System.Security.SecureString]) { return (ConvertFrom-SecureString $t -AsPlainText) } + return $t +} + +function Invoke-MiSql { + param( + [Parameter(Mandatory = $true)][string]$Server, + [Parameter(Mandatory = $true)][string]$Database, + [string]$Query, + [int]$QueryTimeout = 0 + ) + Invoke-Sqlcmd -ServerInstance $Server -Database $Database -AccessToken (Get-DbAccessToken) ` + -Query $Query -ConnectionTimeout 30 -QueryTimeout $QueryTimeout -ErrorAction Stop +} + +function Invoke-FabricApi { + param( + [Parameter(Mandatory = $true)][ValidateSet('GET', 'POST', 'DELETE')][string]$Method, + [Parameter(Mandatory = $true)][string]$Path, + [object]$Body + ) + $url = "$FabricApi/$($Path.TrimStart('/'))" + $azArgs = @('rest', '--method', $Method, '--url', $url, '--resource', 'https://api.fabric.microsoft.com') + if ($Body) { + $json = ($Body | ConvertTo-Json -Depth 10 -Compress) + $azArgs += @('--headers', 'Content-Type=application/json', '--body', $json) + } + $raw = az @azArgs 2>&1 + if ($LASTEXITCODE -ne 0) { throw "Fabric API $Method $Path failed: $raw" } + if ([string]::IsNullOrWhiteSpace($raw)) { return $null } + return ($raw | ConvertFrom-Json) +} + +# ───────────────────────────────────────────── +# 0. Resolve the attendee + shared SQL MI +# ───────────────────────────────────────────── +if (-not $AllowedEntraUserIds -or $AllowedEntraUserIds.Count -eq 0) { throw "No AllowedEntraUserIds supplied." } +$attendeeId = $AllowedEntraUserIds[0] +$user = Get-MhhLabUser -UserId $attendeeId +$short = ($user.ShortName -replace '[^a-zA-Z0-9]', '').ToLower() +if ([string]::IsNullOrWhiteSpace($short)) { $short = 'u' + (Get-MhhStableHash -Value $attendeeId -Length 12) } +$upn = $user.UserPrincipalName + +$sqlDb = "TailspinToys_$short" +$feedbackDb = "TailspinToysFeedback_$short" + +$mi = @(az sql mi list -g $SharedResourceGroup -o json | ConvertFrom-Json) +if (-not $mi -or $mi.Count -eq 0) { throw "No shared SQL Managed Instance found in '$SharedResourceGroup'. Did the shared hook run?" } +$miFqdn = $mi[0].fullyQualifiedDomainName +$publicFqdn = $miFqdn -replace '^([^.]+)\.', '$1.public.' +$server = "$publicFqdn,3342" + +# ───────────────────────────────────────────── +# 1. Per-attendee ARM resources (CSV storage) into the attendee's RG +# ───────────────────────────────────────────── +Update-MhhToken +$rgResult = Invoke-MhhDeploymentWithRegionFallback ` + -PreferredLocations $PreferredLocation ` + -ResourceGroupName $ResourceGroupName ` + -RgOwnerEntraObjectIds $AllowedEntraUserIds ` + -TemplateFile (Join-Path $PSScriptRoot 'main.bicep') ` + -TemplateParameterObject @{ attendeeObjectId = $attendeeId } ` + -DeploymentNamePrefix 'lab' + +$userStorage = [string]$rgResult.Outputs['storageAccountName'] +$userContainer = [string]$rgResult.Outputs['containerName'] + +# Upload the employee CSV into the attendee container. +$csvPath = Join-Path $PSScriptRoot 'csvdata/employees_user_data.csv' +if (-not (Test-Path $csvPath)) { throw "Employee CSV not found: $csvPath" } +$userStorageKey = az storage account keys list --resource-group $ResourceGroupName --account-name $userStorage --query "[0].value" -o tsv +az storage blob upload --account-name $userStorage --account-key $userStorageKey ` + --container-name $userContainer --name (Split-Path $csvPath -Leaf) --file $csvPath --overwrite true --only-show-errors | Out-Null +if ($LASTEXITCODE -ne 0) { throw "Failed to upload employee CSV." } + +# ───────────────────────────────────────────── +# 2. Restore the two attendee databases in the shared SQL MI +# ───────────────────────────────────────────── +Update-MhhToken +$storageAccount = az storage account list -g $SharedResourceGroup --query "[0].name" -o tsv +$containerName = 'build' +$storageKey = az storage account keys list --resource-group $SharedResourceGroup --account-name $storageAccount --query "[0].value" -o tsv +$expiry = (Get-Date).ToUniversalTime().AddHours(4).ToString('yyyy-MM-ddTHH:mmZ') +$sasToken = az storage container generate-sas --account-name $storageAccount --name $containerName ` + --account-key $storageKey --permissions rl --https-only --expiry $expiry -o tsv +if ($LASTEXITCODE -ne 0) { throw "Failed to generate container SAS." } + +$credentialName = "https://$storageAccount.blob.core.windows.net/$containerName" +Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 -Query @" +IF EXISTS (SELECT 1 FROM sys.credentials WHERE name = N'$credentialName') + DROP CREDENTIAL [$credentialName]; +CREATE CREDENTIAL [$credentialName] +WITH IDENTITY = 'Shared Access Signature', SECRET = '$sasToken'; +"@ | Out-Null + +$restores = @( + @{ Db = $sqlDb; Bak = $TailspinToysBak }, + @{ Db = $feedbackDb; Bak = $TailspinToysFeedbackBak } +) +foreach ($r in $restores) { + $exists = (Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 ` + -Query "SELECT COUNT(*) AS C FROM sys.databases WHERE name = N'$($r.Db)'").C + if ($exists -gt 0) { + Write-Host "[lab] Database '$($r.Db)' already exists. Skipping restore." + continue + } + $url = "https://$storageAccount.blob.core.windows.net/$containerName/$($r.Bak)" + Write-Host "[lab] Restoring '$($r.Db)'." + Invoke-MiSql -Server $server -Database 'master' -Query "RESTORE DATABASE [$($r.Db)] FROM URL = N'$url';" | Out-Null +} + +# ───────────────────────────────────────────── +# 3. Attendee login/user (db_owner) + product in each database +# ───────────────────────────────────────────── +Update-MhhToken +Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 -Query @" +IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = N'$upn') + CREATE LOGIN [$upn] FROM EXTERNAL PROVIDER; +"@ | Out-Null + +$productSqlTemplate = Get-Content -Raw (Join-Path $PSScriptRoot 'sql/InsertProduct.sql') +foreach ($db in @($sqlDb, $feedbackDb)) { + Invoke-MiSql -Server $server -Database $db -QueryTimeout 60 -Query @" +IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N'$upn') + CREATE USER [$upn] FROM LOGIN [$upn]; +ALTER ROLE [db_owner] ADD MEMBER [$upn]; +"@ | Out-Null +} + +# Ensure the Fabric Space Ranger product exists in the sales database (needed by the +# shared proc / Agent job, which read Product from each per-attendee database). +$productExists = (Invoke-MiSql -Server $server -Database $sqlDb -QueryTimeout 60 ` + -Query "SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.Product WHERE ProductSKU = '9000-FABRIC-RANGER') THEN 1 ELSE 0 END AS C").C +if ($productExists -ne 1) { + $productSql = $productSqlTemplate -replace '##db-name##', $sqlDb + Invoke-MiSql -Server $server -Database 'master' -Query $productSql | Out-Null + Write-Host "[lab] Inserted Fabric Space Ranger product into '$sqlDb'." +} + +# ───────────────────────────────────────────── +# 4. Fabric workspace on the shared capacity + attendee as Member +# ───────────────────────────────────────────── +Update-MhhToken +$capacityName = az resource list -g $SharedResourceGroup --resource-type 'Microsoft.Fabric/capacities' --query "[0].name" -o tsv +$capacity = (Invoke-FabricApi -Method GET -Path 'capacities').value | Where-Object { $_.displayName -eq $capacityName } | Select-Object -First 1 +if (-not $capacity) { throw "Fabric capacity '$capacityName' not visible via the Fabric API." } + +$workspaceName = "Workspace_$short" +$workspace = (Invoke-FabricApi -Method GET -Path 'workspaces').value | Where-Object { $_.displayName -eq $workspaceName } | Select-Object -First 1 +if (-not $workspace) { + Write-Host "[lab] Creating Fabric workspace '$workspaceName'." + $workspace = Invoke-FabricApi -Method POST -Path 'workspaces' -Body @{ + displayName = $workspaceName + capacityId = $capacity.id + } +} +try { + Invoke-FabricApi -Method POST -Path "workspaces/$($workspace.id)/roleAssignments" -Body @{ + principal = @{ id = $attendeeId; type = 'User' } + role = 'Member' + } | Out-Null +} +catch { + Write-Warning "[lab] Workspace role assignment skipped: $($_.Exception.Message)" +} + +# ───────────────────────────────────────────── +# 5. Attendee credentials +# ───────────────────────────────────────────── +@{ HackboxCredential = @{ name = 'Sales Database'; value = $sqlDb; note = 'Your TailspinToys database on the shared SQL MI' } } +@{ HackboxCredential = @{ name = 'Feedback Database'; value = $feedbackDb; note = 'Your feedback database' } } +@{ HackboxCredential = @{ name = 'Fabric Workspace'; value = $workspaceName; note = 'Your Fabric workspace' } } +@{ HackboxCredential = @{ name = 'Employee CSV Storage'; value = $userStorage; note = "Container '$userContainer'" } } + +Write-Host "[lab] Deployment complete for $upn." diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/main.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/main.bicep new file mode 100644 index 000000000..bf97e0504 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/main.bicep @@ -0,0 +1,126 @@ +// Phase 1: subscription-scoped entry point. Creates the resource group and deploys +// the ARM infrastructure previously described by the Terraform root module. +// The non-ARM pieces (Entra users, SQL MI Directory-Readers grant + AD admin, +// Fabric gateway/workspaces/role-assignments, blob uploads, data loading) are +// deliberately out of scope here and handled by deployment orchestration. + +targetScope = 'subscription' + +@description('Azure region for all resources.') +param location string + +@description('Lab/environment name.') +param envName string + +@description('SQL Managed Instance SQL admin login.') +param sqlAdminLogin string + +@description('SQL Managed Instance administrator password.') +@secure() +param sqlPassword string + +@description('Entra admin login used as the Fabric capacity administrator.') +param sqlMiEntraAdminLogin string + +@description('Number of user containers/workspaces to size the environment for.') +@minValue(1) +param userCount int = 5 + +@description('Base database the webshop App Service connects to.') +param appServiceSqlDatabase string = 'TailspinToys_Demo_Final' + +var commonTags = { + SecurityControl: 'Ignore' +} + +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: 'rg-sqlhack-${envName}' + location: location + tags: commonTags +} + +module vnet 'modules/vnet.bicep' = { + scope: resourceGroup + name: 'vnet' + params: { + location: location + envName: envName + tags: commonTags + } +} + +module sqlManagedInstance 'modules/sqlManagedInstance.bicep' = { + scope: resourceGroup + name: 'sqlManagedInstance' + params: { + location: location + envName: envName + instanceName: 'sqlhackmi-${envName}' + subnetId: vnet.outputs.managedInstanceSubnetId + administratorLogin: sqlAdminLogin + administratorLoginPassword: sqlPassword + tags: commonTags + } +} + +module appService 'modules/appService.bicep' = { + scope: resourceGroup + name: 'appService' + params: { + location: location + envName: envName + subnetId: vnet.outputs.appServiceSubnetId + sqlDatabase: appServiceSqlDatabase + sqlServerFqdn: sqlManagedInstance.outputs.fqdn + sqlAdminLogin: sqlAdminLogin + sqlPassword: sqlPassword + tags: commonTags + } +} + +module storageBackups 'modules/storageBackups.bicep' = { + scope: resourceGroup + name: 'storageBackups' + params: { + location: location + envName: envName + containerName: 'build' + tags: commonTags + } +} + +module userDataStorage 'modules/userDataStorage.bicep' = { + scope: resourceGroup + name: 'userDataStorage' + params: { + location: location + envName: envName + userCount: userCount + containerNamePrefix: 'container' + tags: commonTags + } +} + +module fabricCapacity 'modules/fabricCapacity.bicep' = { + scope: resourceGroup + name: 'fabricCapacity' + params: { + location: location + envName: envName + skuName: 'F2' + administrationMembers: [ + sqlMiEntraAdminLogin + ] + tags: commonTags + } +} + +output resourceGroupName string = resourceGroup.name +output vnetName string = vnet.outputs.vnetName +output sqlManagedInstanceName string = sqlManagedInstance.outputs.name +output sqlManagedInstanceFqdn string = sqlManagedInstance.outputs.fqdn +output AZURE_APP_SERVICE_WEB_APP_NAME string = appService.outputs.webAppName +output appServiceDefaultHostname string = appService.outputs.defaultHostname +output backupStorageAccountName string = storageBackups.outputs.storageAccountName +output userDataStorageAccountName string = userDataStorage.outputs.storageAccountName +output fabricCapacityName string = fabricCapacity.outputs.capacityName diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/appService.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/appService.bicep new file mode 100644 index 000000000..e7f5a7bb2 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/appService.bicep @@ -0,0 +1,97 @@ +// Linux App Service (webshop). Faithful Bicep port of infra/modules/app_service_linux. + +@description('Azure region.') +param location string + +@description('Lab/environment name (used for tagging and default names).') +param envName string + +@description('Resource ID of the delegated snet-appservice subnet for VNet integration.') +param subnetId string + +@description('App Service Plan name. Defaults to asp-sqlhack-.') +param servicePlanName string = 'asp-sqlhack-${envName}' + +@description('Web App name. Defaults to app-sqlhack-.') +param webAppName string = 'app-sqlhack-${envName}' + +@description('App Service Plan SKU.') +param sku string = 'B1' + +@description('SQL database the webshop connects to.') +param sqlDatabase string + +@description('SQL Managed Instance FQDN.') +param sqlServerFqdn string + +@description('SQL login used by the webshop.') +param sqlAdminLogin string + +@description('SQL password used by the webshop.') +@secure() +param sqlPassword string + +@description('Tags to apply to all resources.') +param tags object = {} + +var mergedTags = union(tags, { + environment: envName +}) + +resource servicePlan 'Microsoft.Web/serverfarms@2023-12-01' = { + name: servicePlanName + location: location + tags: mergedTags + kind: 'linux' + sku: { + name: sku + } + properties: { + reserved: true + } +} + +resource webApp 'Microsoft.Web/sites@2023-12-01' = { + name: webAppName + location: location + tags: mergedTags + properties: { + serverFarmId: servicePlan.id + virtualNetworkSubnetId: subnetId + siteConfig: { + linuxFxVersion: 'NODE|24-lts' + appSettings: [ + { + name: 'SQL_DATABASE' + value: sqlDatabase + } + { + name: 'SQL_PASSWORD' + value: sqlPassword + } + { + name: 'SQL_SERVER' + value: sqlServerFqdn + } + { + name: 'SQL_USER' + value: sqlAdminLogin + } + { + name: 'SCM_DO_BUILD_DURING_DEPLOYMENT' + value: 'true' + } + { + name: 'PORT' + value: '3000' + } + ] + } + } +} + +output servicePlanId string = servicePlan.id +output servicePlanName string = servicePlan.name +output webAppId string = webApp.id +output webAppName string = webApp.name +output defaultHostname string = webApp.properties.defaultHostName diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/fabricCapacity.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/fabricCapacity.bicep new file mode 100644 index 000000000..5681a2c9a --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/fabricCapacity.bicep @@ -0,0 +1,40 @@ +// Microsoft Fabric capacity. Faithful Bicep port of the ARM-deployable part of +// infra/modules/fabric_platform. NOTE: the Fabric VNet gateway, per-user +// workspaces and workspace/gateway role assignments are Fabric control-plane +// objects (not ARM) and are handled by orchestration (Phase 2). + +@description('Azure region.') +param location string + +@description('Lab/environment name (used for the generated capacity name).') +param envName string + +@description('Fabric capacity SKU name (e.g. F2, F32).') +param skuName string = 'F2' + +@description('Fabric capacity administrator identities (UPNs or object IDs).') +param administrationMembers array + +@description('Tags to apply to all resources.') +param tags object = {} + +var normalizedEnv = toLower(replace(envName, '-', '')) +var capacityName = substring('fab${normalizedEnv}', 0, min(length('fab${normalizedEnv}'), 63)) + +resource capacity 'Microsoft.Fabric/capacities@2023-11-01' = { + name: capacityName + location: location + tags: tags + sku: { + name: skuName + tier: 'Fabric' + } + properties: { + administration: { + members: administrationMembers + } + } +} + +output capacityId string = capacity.id +output capacityName string = capacity.name diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/sqlManagedInstance.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/sqlManagedInstance.bicep new file mode 100644 index 000000000..8109a0e6a --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/sqlManagedInstance.bicep @@ -0,0 +1,61 @@ +// SQL Managed Instance. Faithful Bicep port of infra/modules/sql_managed_instance. +// NOTE: the Entra "Directory Readers" grant on the MI identity and the AD admin +// assignment are NOT ARM operations and are handled by orchestration (Phase 2), +// so they are intentionally omitted here. The inbound 3342 NSG rule now lives in +// the vnet module. + +@description('Azure region.') +param location string + +@description('Lab/environment name (used for tagging).') +param envName string + +@description('SQL Managed Instance name.') +param instanceName string + +@description('Resource ID of the delegated ManagedInstance subnet.') +param subnetId string + +@description('SQL administrator login.') +param administratorLogin string + +@description('SQL administrator login password.') +@secure() +param administratorLoginPassword string + +@description('Tags to apply to all resources.') +param tags object = {} + +var mergedTags = union(tags, { + environment: envName +}) + +resource managedInstance 'Microsoft.Sql/managedInstances@2023-08-01-preview' = { + name: instanceName + location: location + tags: mergedTags + sku: { + name: 'GP_Gen5' + tier: 'GeneralPurpose' + } + identity: { + type: 'SystemAssigned' + } + properties: { + administratorLogin: administratorLogin + administratorLoginPassword: administratorLoginPassword + subnetId: subnetId + licenseType: 'LicenseIncluded' + vCores: 4 + storageSizeInGB: 96 + requestedBackupStorageRedundancy: 'Local' + proxyOverride: 'Default' + publicDataEndpointEnabled: true + databaseFormat: 'AlwaysUpToDate' + } +} + +output id string = managedInstance.id +output name string = managedInstance.name +output fqdn string = managedInstance.properties.fullyQualifiedDomainName +output identityPrincipalId string = managedInstance.identity.principalId diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/storageBackups.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/storageBackups.bicep new file mode 100644 index 000000000..50a8b0ec0 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/storageBackups.bicep @@ -0,0 +1,57 @@ +// Storage account for database backup (.bak) files. Faithful Bicep port of +// infra/modules/storage_account_backups. NOTE: uploading the .bak blobs is not an +// ARM operation and is handled by orchestration (Phase 2); only the account and +// container are created here. + +@description('Azure region.') +param location string + +@description('Lab/environment name (used for tagging and the generated account name).') +param envName string + +@description('Blob container name for backups.') +param containerName string = 'build' + +@description('Optional explicit storage account name. Defaults to stsqlhack.') +param storageAccountName string = '' + +@description('Tags to apply to all resources.') +param tags object = {} + +var normalizedEnv = toLower(replace(envName, '-', '')) +var generatedName = substring('stsqlhack${normalizedEnv}', 0, min(length('stsqlhack${normalizedEnv}'), 24)) +var effectiveName = empty(storageAccountName) ? generatedName : storageAccountName + +var mergedTags = union(tags, { + environment: envName +}) + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: effectiveName + location: location + tags: mergedTags + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowBlobPublicAccess: false + minimumTlsVersion: 'TLS1_2' + } +} + +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = { + parent: storageAccount + name: 'default' +} + +resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = { + parent: blobService + name: containerName + properties: { + publicAccess: 'None' + } +} + +output storageAccountName string = storageAccount.name +output containerName string = container.name diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/userDataStorage.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/userDataStorage.bicep new file mode 100644 index 000000000..633eadebd --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/userDataStorage.bicep @@ -0,0 +1,66 @@ +// Storage account with per-user containers for employee CSV data. Faithful Bicep +// port of infra/modules/user_data_storage. NOTE: uploading the CSV blobs and the +// user/group RBAC role assignments depend on Entra principals and blob content, +// neither of which is created here; those are handled by orchestration (Phase 2). + +@description('Azure region.') +param location string + +@description('Lab/environment name (used for tagging and the generated account name).') +param envName string + +@description('Number of per-user containers to create.') +@minValue(1) +param userCount int + +@description('Prefix for generated container names (e.g. container001).') +param containerNamePrefix string = 'container' + +@description('Optional explicit storage account name. Defaults to stuserdata.') +param storageAccountName string = '' + +@description('Tags to apply to all resources.') +param tags object = {} + +var normalizedEnv = toLower(replace(envName, '-', '')) +var generatedName = substring('stuserdata${normalizedEnv}', 0, min(length('stuserdata${normalizedEnv}'), 24)) +var effectiveName = empty(storageAccountName) ? generatedName : storageAccountName + +var mergedTags = union(tags, { + environment: envName +}) + +var containerNames = [for i in range(0, userCount): '${containerNamePrefix}${padLeft(string(i + 1), 3, '0')}'] + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: effectiveName + location: location + tags: mergedTags + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowBlobPublicAccess: false + minimumTlsVersion: 'TLS1_2' + isHnsEnabled: true + } +} + +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = { + parent: storageAccount + name: 'default' +} + +resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = [ + for name in containerNames: { + parent: blobService + name: name + properties: { + publicAccess: 'None' + } + } +] + +output storageAccountName string = storageAccount.name +output containerNames array = containerNames diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/vnet.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/vnet.bicep new file mode 100644 index 000000000..ccccc2340 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/infra/modules/vnet.bicep @@ -0,0 +1,568 @@ +// Virtual network with all subnets, NSGs and the SQL MI route table. +// Faithful Bicep port of infra/modules/vnet (main.tf, nsg.tf, route_table.tf). + +@description('Azure region.') +param location string + +@description('AZD/lab environment name (used for tagging).') +param envName string + +@description('Name of the virtual network.') +param vnetName string = 'SQLHACK-SHARED-vnet' + +@description('Address space for the virtual network.') +param addressSpace array = [ + '10.0.0.0/16' +] + +@description('Tags to apply to all resources.') +param tags object = {} + +var mergedTags = union(tags, { + environment: envName +}) + +// ───────────────────────────────────────────── +// NSGs +// ───────────────────────────────────────────── + +// ManagedInstance subnet NSG. In the Terraform version the NSG was created empty +// and the 3342 rule was added by the SQL MI module; here it is inlined. +resource sqlMiNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'nsg-sqlhackmi' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-sqlmi-inbound-tcp-3342-internet' + properties: { + priority: 1000 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3342' + sourceAddressPrefix: 'Internet' + destinationAddressPrefix: '*' + } + } + ] + } +} + +resource bastionNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'sqlhack-shared-bastion-nsg' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-bastion-inbound-https-internet' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'Internet' + destinationAddressPrefix: '*' + } + } + { + name: 'allow-bastion-inbound-gateway-manager' + properties: { + priority: 110 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'GatewayManager' + destinationAddressPrefix: '*' + } + } + { + name: 'allow-bastion-inbound-azure-lb' + properties: { + priority: 120 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: '*' + } + } + { + name: 'allow-bastion-inbound-host-comm' + properties: { + priority: 130 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: [ + '8080' + '5701' + ] + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'deny-bastion-inbound-all' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + } + } + { + name: 'allow-bastion-outbound-ssh-rdp' + properties: { + priority: 100 + direction: 'Outbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: [ + '22' + '3389' + ] + sourceAddressPrefix: '*' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'allow-bastion-outbound-azure-cloud' + properties: { + priority: 110 + direction: 'Outbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'AzureCloud' + } + } + { + name: 'allow-bastion-outbound-host-comm' + properties: { + priority: 120 + direction: 'Outbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: [ + '8080' + '5701' + ] + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'allow-bastion-outbound-session-info' + properties: { + priority: 130 + direction: 'Outbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '80' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'Internet' + } + } + ] + } +} + +resource managementNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'sqlhack-shared-mgmt-nsg' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-mgmt-inbound-vnet' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'allow-mgmt-inbound-bastion' + properties: { + priority: 110 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRanges: [ + '22' + '3389' + ] + sourceAddressPrefix: '10.0.4.0/24' + destinationAddressPrefix: '*' + } + } + { + name: 'deny-mgmt-inbound-all' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + } + } + ] + } +} + +resource teamJumpboxNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'sqlhack-shared-jump-nsg' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-jump-inbound-bastion' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRanges: [ + '22' + '3389' + ] + sourceAddressPrefix: '10.0.4.0/24' + destinationAddressPrefix: '*' + } + } + { + name: 'allow-jump-inbound-vnet' + properties: { + priority: 110 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'deny-jump-inbound-all' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + } + } + ] + } +} + +resource appServiceNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'SQLHACK-appservice-nsg' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-appservice-inbound-vnet' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'allow-appservice-inbound-alb' + properties: { + priority: 110 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: '*' + } + } + { + name: 'deny-appservice-inbound-all' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + } + } + ] + } +} + +resource fabricNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'sqlhack-shared-fabric-nsg' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-fabric-inbound-vnet' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'deny-fabric-inbound-all' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + } + } + ] + } +} + +resource streamingNsg 'Microsoft.Network/networkSecurityGroups@2023-11-01' = { + name: 'SQLHACK-streaming-nsg' + location: location + tags: mergedTags + properties: { + securityRules: [ + { + name: 'allow-streaming-inbound-vnet' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'deny-streaming-inbound-all' + properties: { + priority: 4096 + direction: 'Inbound' + access: 'Deny' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + } + } + ] + } +} + +// ───────────────────────────────────────────── +// Route table for the ManagedInstance subnet +// ───────────────────────────────────────────── +resource sqlMiRouteTable 'Microsoft.Network/routeTables@2023-11-01' = { + name: 'rt-sqlhackmi' + location: location + tags: mergedTags + properties: { + disableBgpRoutePropagation: false + } +} + +// ───────────────────────────────────────────── +// Virtual network with inline subnets +// ───────────────────────────────────────────── +resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = { + name: vnetName + location: location + tags: mergedTags + properties: { + addressSpace: { + addressPrefixes: addressSpace + } + subnets: [ + { + name: 'GatewaySubnet' + properties: { + addressPrefixes: [ + '10.0.0.0/24' + ] + defaultOutboundAccess: false + } + } + { + name: 'ManagedInstance' + properties: { + addressPrefixes: [ + '10.0.1.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: sqlMiNsg.id + } + routeTable: { + id: sqlMiRouteTable.id + } + delegations: [ + { + name: 'sql-mi-delegation' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + } + } + { + name: 'Management' + properties: { + addressPrefixes: [ + '10.0.2.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: managementNsg.id + } + } + } + { + name: 'TeamJumpbox' + properties: { + addressPrefixes: [ + '10.0.3.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: teamJumpboxNsg.id + } + } + } + { + name: 'AzureBastionSubnet' + properties: { + addressPrefixes: [ + '10.0.4.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: bastionNsg.id + } + } + } + { + name: 'snet-appservice' + properties: { + addressPrefixes: [ + '10.0.5.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: appServiceNsg.id + } + delegations: [ + { + name: 'appservice-delegation' + properties: { + serviceName: 'Microsoft.Web/serverFarms' + } + } + ] + } + } + { + name: 'fabric_vnet' + properties: { + addressPrefixes: [ + '10.0.6.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: fabricNsg.id + } + delegations: [ + { + name: 'powerplatform-delegation' + properties: { + serviceName: 'Microsoft.PowerPlatform/vnetaccesslinks' + } + } + ] + } + } + { + name: 'StreamingVnet' + properties: { + addressPrefixes: [ + '10.0.7.0/24' + ] + defaultOutboundAccess: false + networkSecurityGroup: { + id: streamingNsg.id + } + } + } + ] + } +} + +output vnetName string = vnet.name +output vnetId string = vnet.id +output managedInstanceSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, 'ManagedInstance') +output appServiceSubnetId string = resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, 'snet-appservice') +output fabricSubnetName string = 'fabric_vnet' +output sqlMiNsgName string = sqlMiNsg.name diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/lab-defaults.json b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/lab-defaults.json new file mode 100644 index 000000000..a09f1c396 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/lab-defaults.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/MicroHack/refs/heads/main/lab-defaults-schema.json", + "groups": [ "" ], + "deploymentType": "resourcegroup", + "labsPerSubscription": 42, + "preferredLocation": "swedencentral, westeurope, northeurope", + "estimatedDailyCostsUsd": 5.0, + "estimatedSharedDeploymentDailyCostsUsd": 220.0 +} diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/main.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/main.bicep new file mode 100644 index 000000000..0ca4d27bb --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/main.bicep @@ -0,0 +1,64 @@ +// Per-attendee ARM resources, deployed by deploy-lab.ps1 into the attendee's own +// resource group. The attendee's two databases and Fabric workspace are not ARM +// resources and are created by deploy-lab.ps1 against the shared MI/capacity. +// The employee CSV blob is uploaded by deploy-lab.ps1 (not an ARM operation). + +@description('Azure region.') +param location string + +@description('Entra object ID of the attendee, granted data access to the CSV storage.') +param attendeeObjectId string + +@description('Blob container name for the attendee employee CSV.') +param containerName string = 'container' + +@description('Optional explicit storage account name. Defaults to a unique per-RG name.') +param storageAccountName string = 'stusr${uniqueString(resourceGroup().id)}' + +var commonTags = { + SecurityControl: 'Ignore' +} + +// Storage Blob Data Owner +var blobDataOwnerRoleId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: storageAccountName + location: location + tags: commonTags + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + allowBlobPublicAccess: false + minimumTlsVersion: 'TLS1_2' + isHnsEnabled: true + } +} + +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = { + parent: storageAccount + name: 'default' +} + +resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = { + parent: blobService + name: containerName + properties: { + publicAccess: 'None' + } +} + +resource blobOwner 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(storageAccount.id, attendeeObjectId, blobDataOwnerRoleId) + scope: storageAccount + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', blobDataOwnerRoleId) + principalId: attendeeObjectId + principalType: 'User' + } +} + +output storageAccountName string = storageAccount.name +output containerName string = container.name diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/shared-deploy-lab.ps1 b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/shared-deploy-lab.ps1 new file mode 100644 index 000000000..6c5bd3401 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/shared-deploy-lab.ps1 @@ -0,0 +1,333 @@ +<# +.SYNOPSIS +Hook that runs once per subscription before any deploy-lab.ps1 run starts. +.DESCRIPTION +Hook that runs before the per-user deploy-lab.ps1 runs are started. It can be used to deploy shared resources on a subscription level +(e.g. a hub VNet) or to prepare the subscription once instead of once per lab (e.g. registering resource providers). +If it fails for any subscription, no deploy-lab.ps1 runs at all. Emitted HackboxCredential entries are stored for every lab in this subscription. +.PARAMETER SubscriptionId +Specifies the Azure subscription that contains the lab resources. +.PARAMETER PreferredLocation +Specifies the preferred Azure regions (ordered by preference) for resource deployment. +.PARAMETER AllowedEntraUserIds +Entra user object IDs of every participant holding a lab in this subscription. +#> +param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string[]]$PreferredLocation = @(), + + [Parameter(Mandatory = $false)] + [string[]]$AllowedEntraUserIds = @() +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# ───────────────────────────────────────────── +# Constants +# ───────────────────────────────────────────── +$SharedResourceGroup = 'rg-shared' +$SqlAdminLogin = 'sqlmiadmin' +$FabricApi = 'https://api.fabric.microsoft.com/v1' +$GraphApi = 'https://graph.microsoft.com/v1.0' +$RequiredProviders = @('Microsoft.Sql', 'Microsoft.Fabric', 'Microsoft.Storage', 'Microsoft.Web', 'Microsoft.Network') + +# Demo databases restored once for the whole subscription: DB name -> backup file. +$TailspinToysBak = 'tailspintoys_before_launch.bak' +$TailspinToysFeedbackBak = 'tailspintoysfeedback_before_launch.bak' +$DemoDatabases = [ordered]@{ + 'TailspinToys_Demo_Final' = $TailspinToysBak + 'TailspinToys_Demo_Mirroring' = $TailspinToysBak + 'TailspinToysFeedback_Demo_Final' = $TailspinToysFeedbackBak + 'TailspinToysFeedback_Demo_Mirrored' = $TailspinToysFeedbackBak +} + +# ───────────────────────────────────────────── +# Helpers +# ───────────────────────────────────────────── +if (-not (Get-Module -ListAvailable -Name SqlServer)) { + Install-Module -Name SqlServer -Scope CurrentUser -Repository PSGallery -Force -AllowClobber +} +Import-Module SqlServer -ErrorAction Stop + +function Get-DbAccessToken { + $t = (Get-AzAccessToken -ResourceUrl 'https://database.windows.net/').Token + if ($t -is [System.Security.SecureString]) { return (ConvertFrom-SecureString $t -AsPlainText) } + return $t +} + +function Invoke-MiSql { + param( + [Parameter(Mandatory = $true)][string]$Server, + [Parameter(Mandatory = $true)][string]$Database, + [string]$Query, + [string]$InputFile, + [int]$QueryTimeout = 0 + ) + $splat = @{ + ServerInstance = $Server + Database = $Database + AccessToken = (Get-DbAccessToken) + ConnectionTimeout = 30 + QueryTimeout = $QueryTimeout + ErrorAction = 'Stop' + } + if ($InputFile) { $splat['InputFile'] = $InputFile } else { $splat['Query'] = $Query } + Invoke-Sqlcmd @splat +} + +function Invoke-FabricApi { + param( + [Parameter(Mandatory = $true)][ValidateSet('GET', 'POST', 'DELETE')][string]$Method, + [Parameter(Mandatory = $true)][string]$Path, + [object]$Body + ) + $url = "$FabricApi/$($Path.TrimStart('/'))" + $azArgs = @('rest', '--method', $Method, '--url', $url, '--resource', 'https://api.fabric.microsoft.com') + if ($Body) { + $json = ($Body | ConvertTo-Json -Depth 10 -Compress) + $azArgs += @('--headers', 'Content-Type=application/json', '--body', $json) + } + $raw = az @azArgs 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "Fabric API $Method $Path failed: $raw" + } + if ([string]::IsNullOrWhiteSpace($raw)) { return $null } + return ($raw | ConvertFrom-Json) +} + +function Ensure-DirectoryReadersForIdentity { + param([Parameter(Mandatory = $true)][string]$PrincipalId) + + # Directory Readers well-known template id. + $templateId = '88d8e3e3-8f55-4a1e-953a-9b9898b8876b' + # Activate the directory role if it is only present as a template. + $roles = (az rest --method GET --url "$GraphApi/directoryRoles" --resource 'https://graph.microsoft.com' 2>$null | ConvertFrom-Json).value + $role = $roles | Where-Object { $_.roleTemplateId -eq $templateId } | Select-Object -First 1 + if (-not $role) { + $activated = az rest --method POST --url "$GraphApi/directoryRoles" --resource 'https://graph.microsoft.com' ` + --headers 'Content-Type=application/json' --body (@{ roleTemplateId = $templateId } | ConvertTo-Json -Compress) 2>&1 | ConvertFrom-Json + $role = $activated + } + # Is the identity already a member? + $members = (az rest --method GET --url "$GraphApi/directoryRoles/$($role.id)/members" --resource 'https://graph.microsoft.com' 2>$null | ConvertFrom-Json).value + if ($members | Where-Object { $_.id -eq $PrincipalId }) { + Write-Host "[shared] SQL MI identity already has Directory Readers." + return + } + $ref = @{ '@odata.id' = "$GraphApi/directoryObjects/$PrincipalId" } | ConvertTo-Json -Compress + az rest --method POST --url "$GraphApi/directoryRoles/$($role.id)/members/`$ref" --resource 'https://graph.microsoft.com' ` + --headers 'Content-Type=application/json' --body $ref 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to add SQL MI identity to Directory Readers." } + Write-Host "[shared] Granted Directory Readers to SQL MI identity. Waiting for propagation." + Start-Sleep -Seconds 60 +} + +# ───────────────────────────────────────────── +# 0. Location + resource providers +# ───────────────────────────────────────────── +$locations = @($PreferredLocation | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim() } | Where-Object { $_ }) +if ($locations.Count -eq 0) { throw "No PreferredLocation supplied." } +$location = $locations[0] +Write-Host "[shared] Using location '$location' (SQL MI + Fabric F32 do not auto-fall-back across regions; re-run with a different preferredLocation order if capacity is unavailable)." + +foreach ($rp in $RequiredProviders) { + Register-AzResourceProvider -ProviderNamespace $rp -ErrorAction SilentlyContinue | Out-Null +} + +# ───────────────────────────────────────────── +# 1. Shared resource group + Owner for every attendee in this subscription +# ───────────────────────────────────────────── +if (-not (Get-AzResourceGroup -Name $SharedResourceGroup -ErrorAction SilentlyContinue)) { + New-AzResourceGroup -Name $SharedResourceGroup -Location $location -Tag @{ SecurityControl = 'Ignore' } | Out-Null + Write-Host "[shared] Created resource group '$SharedResourceGroup'." +} +$rgId = (Get-AzResourceGroup -Name $SharedResourceGroup).ResourceId +foreach ($uid in ($AllowedEntraUserIds | Where-Object { $_ })) { + if (-not (Get-AzRoleAssignment -ObjectId $uid -Scope $rgId -RoleDefinitionName 'Owner' -ErrorAction SilentlyContinue)) { + New-AzRoleAssignment -ObjectId $uid -Scope $rgId -RoleDefinitionName 'Owner' -ErrorAction SilentlyContinue | Out-Null + } +} + +# ───────────────────────────────────────────── +# 2. Resolve the deploying service principal (becomes SQL MI Entra admin) +# ───────────────────────────────────────────── +$spObjectId = (Get-AzADServicePrincipal -ApplicationId (Get-AzContext).Account.Id -ErrorAction SilentlyContinue).Id +if (-not $spObjectId) { + $spObjectId = az ad sp show --id (Get-AzContext).Account.Id --query id -o tsv 2>$null +} +if (-not $spObjectId) { throw "Could not resolve the deploying service principal object id." } + +$fabricAdminMembers = @($spObjectId) + ($AllowedEntraUserIds | Where-Object { $_ }) | Select-Object -Unique +$sqlPassword = New-MhhStablePassword -Purpose 'sql-admin' -Length 24 + +# ───────────────────────────────────────────── +# 3. Deploy the shared ARM stack (async; SQL MI can exceed the 90-min command limit) +# ───────────────────────────────────────────── +$depName = "shared-$(Get-Date -f yyyyMMddHHmmss)" +Write-Host "[shared] Submitting shared.bicep deployment '$depName' into '$SharedResourceGroup'." +New-AzResourceGroupDeployment ` + -Name $depName ` + -ResourceGroupName $SharedResourceGroup ` + -TemplateFile (Join-Path $PSScriptRoot 'shared.bicep') ` + -TemplateParameterObject @{ + location = $location + sqlAdminLogin = $SqlAdminLogin + sqlPassword = (ConvertTo-SecureString $sqlPassword -AsPlainText -Force) + fabricAdminMembers = $fabricAdminMembers +} ` + -AsJob | Out-Null + +do { + Start-Sleep -Seconds 30 + Update-MhhToken + $state = (Get-AzResourceGroupDeployment -ResourceGroupName $SharedResourceGroup -Name $depName -ErrorAction SilentlyContinue).ProvisioningState + Write-Host "[shared] deployment state: $state" +} while ($state -notin 'Succeeded', 'Failed', 'Canceled') + +if ($state -ne 'Succeeded') { + throw "Shared deployment '$depName' ended in state '$state'. See the deployment operations in '$SharedResourceGroup'." +} + +$out = (Get-AzResourceGroupDeployment -ResourceGroupName $SharedResourceGroup -Name $depName).Outputs +$miName = $out.sqlManagedInstanceName.Value +$miFqdn = $out.sqlManagedInstanceFqdn.Value +$miIdentity = $out.sqlManagedInstanceIdentityPrincipalId.Value +$capacityName = $out.fabricCapacityName.Value +$vnetName = $out.vnetName.Value +$fabricSubnet = $out.fabricSubnetName.Value +$storageAccount = $out.backupStorageAccountName.Value +$containerName = $out.backupContainerName.Value +$webshopHost = $out.webshopDefaultHostname.Value + +# Public endpoint FQDN: insert 'public.' after the instance short name; port 3342. +$publicFqdn = $miFqdn -replace '^([^.]+)\.', '$1.public.' +$server = "$publicFqdn,3342" + +# ───────────────────────────────────────────── +# 4. Entra: Directory Readers for the MI identity + MI Entra admin (deploying SP) +# ───────────────────────────────────────────── +Update-MhhToken +Ensure-DirectoryReadersForIdentity -PrincipalId $miIdentity + +$existingAdmin = az sql mi ad-admin list --resource-group $SharedResourceGroup --managed-instance $miName --query "[0].sid" -o tsv 2>$null +if (-not $existingAdmin) { + Write-Host "[shared] Setting SQL MI Entra admin to the deploying principal." + az sql mi ad-admin create --resource-group $SharedResourceGroup --managed-instance $miName ` + --display-name 'MicroHackDeployer' --object-id $spObjectId 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to set SQL MI Entra admin." } +} + +# ───────────────────────────────────────────── +# 5. Upload the .bak files and restore the demo databases +# ───────────────────────────────────────────── +Update-MhhToken +$storageKey = az storage account keys list --resource-group $SharedResourceGroup --account-name $storageAccount --query "[0].value" -o tsv +if ($LASTEXITCODE -ne 0) { throw "Failed to read storage key for '$storageAccount'." } + +foreach ($bak in @($TailspinToysBak, $TailspinToysFeedbackBak)) { + $localBak = Join-Path $PSScriptRoot "databasebackup/$bak" + if (-not (Test-Path $localBak)) { throw "Backup file not found: $localBak" } + Write-Host "[shared] Uploading $bak." + az storage blob upload --account-name $storageAccount --account-key $storageKey ` + --container-name $containerName --name $bak --file $localBak --overwrite true --only-show-errors | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Failed to upload $bak." } +} + +$expiry = (Get-Date).ToUniversalTime().AddHours(4).ToString('yyyy-MM-ddTHH:mmZ') +$sasToken = az storage container generate-sas --account-name $storageAccount --name $containerName ` + --account-key $storageKey --permissions rl --https-only --expiry $expiry -o tsv +if ($LASTEXITCODE -ne 0) { throw "Failed to generate container SAS." } + +$credentialName = "https://$storageAccount.blob.core.windows.net/$containerName" +Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 -Query @" +IF EXISTS (SELECT 1 FROM sys.credentials WHERE name = N'$credentialName') + DROP CREDENTIAL [$credentialName]; +CREATE CREDENTIAL [$credentialName] +WITH IDENTITY = 'Shared Access Signature', SECRET = '$sasToken'; +"@ | Out-Null + +foreach ($db in $DemoDatabases.Keys) { + $exists = (Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 ` + -Query "SELECT COUNT(*) AS C FROM sys.databases WHERE name = N'$db'").C + if ($exists -gt 0) { + Write-Host "[shared] Demo database '$db' already exists. Skipping restore." + continue + } + $url = "https://$storageAccount.blob.core.windows.net/$containerName/$($DemoDatabases[$db])" + Write-Host "[shared] Restoring demo database '$db'." + Invoke-MiSql -Server $server -Database 'master' -Query "RESTORE DATABASE [$db] FROM URL = N'$url';" | Out-Null +} + +# ───────────────────────────────────────────── +# 6. Seed Demo_Final: product, stored procedure, Agent job +# ───────────────────────────────────────────── +$productExists = (Invoke-MiSql -Server $server -Database 'TailspinToys_Demo_Final' -QueryTimeout 60 ` + -Query "SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.Product WHERE ProductSKU = '9000-FABRIC-RANGER') THEN 1 ELSE 0 END AS C").C +if ($productExists -ne 1) { + $productSql = (Get-Content -Raw (Join-Path $PSScriptRoot 'sql/InsertProduct.sql')) -replace '##db-name##', 'TailspinToys_Demo_Final' + Invoke-MiSql -Server $server -Database 'master' -Query $productSql | Out-Null + Write-Host "[shared] Inserted Fabric Space Ranger product into Demo_Final." +} + +Invoke-MiSql -Server $server -Database 'master' -InputFile (Join-Path $PSScriptRoot 'sql/StoredProcedure.sql') | Out-Null +Write-Host "[shared] Ensured stored procedure usp_PurchaseSpaceRanger." + +$jobExists = (Invoke-MiSql -Server $server -Database 'master' -QueryTimeout 60 ` + -Query "SELECT CASE WHEN EXISTS (SELECT 1 FROM msdb.dbo.sysjobs WHERE name = N'2 Fabric Space Ranger Workload') THEN 1 ELSE 0 END AS C").C +if ($jobExists -ne 1) { + Invoke-MiSql -Server $server -Database 'master' -InputFile (Join-Path $PSScriptRoot 'sql/Jobs.sql') | Out-Null + Write-Host "[shared] Created Agent job '2 Fabric Space Ranger Workload'." +} + +# ───────────────────────────────────────────── +# 7. Fabric VNet data gateway + ConnectionCreator for every attendee +# ───────────────────────────────────────────── +Update-MhhToken +$capacity = (Invoke-FabricApi -Method GET -Path 'capacities').value | Where-Object { $_.displayName -eq $capacityName } | Select-Object -First 1 +if (-not $capacity) { throw "Fabric capacity '$capacityName' not visible via the Fabric API (check tenant setting 'Service principals can use Fabric APIs')." } + +$gatewayName = "fabric-gateway-shared" +$gateway = (Invoke-FabricApi -Method GET -Path 'gateways').value | Where-Object { $_.displayName -eq $gatewayName } | Select-Object -First 1 +if (-not $gateway) { + Write-Host "[shared] Creating Fabric VNet data gateway." + $gateway = Invoke-FabricApi -Method POST -Path 'gateways' -Body @{ + type = 'VirtualNetwork' + displayName = $gatewayName + capacityId = $capacity.id + inactivityMinutesBeforeSleep = 30 + numberOfMemberGateways = 1 + virtualNetworkAzureResource = @{ + subscriptionId = $SubscriptionId + resourceGroupName = $SharedResourceGroup + virtualNetworkName = $vnetName + subnetName = $fabricSubnet + } + } +} +foreach ($uid in ($AllowedEntraUserIds | Where-Object { $_ })) { + try { + Invoke-FabricApi -Method POST -Path "gateways/$($gateway.id)/roleAssignments" -Body @{ + principal = @{ id = $uid; type = 'User' } + role = 'ConnectionCreator' + } | Out-Null + } + catch { + Write-Warning "[shared] Gateway role assignment for $uid skipped: $($_.Exception.Message)" + } +} + +# ───────────────────────────────────────────── +# 8. Shared credentials for every attendee's dashboard +# ───────────────────────────────────────────── +@{ HackboxCredential = @{ name = 'SQL MI Public Endpoint'; value = $server; note = 'SSMS: connect with the SQL admin login below' } } +@{ HackboxCredential = @{ name = 'SQL Admin Login'; value = $SqlAdminLogin; note = 'Shared SQL Managed Instance admin' } } +@{ HackboxCredential = @{ name = 'SQL Admin Password'; value = $sqlPassword; note = 'Shared SQL Managed Instance admin password' } } +@{ HackboxCredential = @{ name = 'Webshop URL'; value = "https://$webshopHost"; note = 'Shared Tailspin Toys webshop' } } +@{ HackboxCredential = @{ name = 'Fabric Capacity'; value = $capacityName; note = 'Shared Fabric F32 capacity' } } +@{ HackboxCredential = @{ name = 'Shared Resource Group'; value = $SharedResourceGroup; note = 'Shared by all labs in your subscription' } } + +Write-Host "[shared] Shared deployment complete." diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/shared.bicep b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/shared.bicep new file mode 100644 index 000000000..d0003601f --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/shared.bicep @@ -0,0 +1,104 @@ +// Shared infrastructure for one subscription, deployed once by shared-deploy-lab.ps1 +// into the dedicated rg-shared resource group. Reuses the Phase 1 modules under +// infra/modules. +// +// Non-ARM follow-up performed by shared-deploy-lab.ps1 after this template: +// - SQL MI Directory-Readers grant + Entra admin +// - demo database restores, stored proc, product, Agent job +// - Fabric VNet gateway + gateway role assignments +// Per-attendee resources (user databases, Fabric workspaces) are created by +// deploy-lab.ps1. + +@description('Azure region for the shared resources.') +param location string + +@description('Naming/tag suffix for the shared resources. Kept unique per subscription.') +param envName string = 'sh${uniqueString(subscription().subscriptionId)}' + +@description('SQL Managed Instance SQL admin login.') +param sqlAdminLogin string + +@description('SQL Managed Instance administrator password.') +@secure() +param sqlPassword string + +@description('Fabric capacity SKU name.') +param fabricSkuName string = 'F32' + +@description('Fabric capacity administrator identities (object IDs / UPNs). Must include the deploying principal.') +param fabricAdminMembers array + +@description('Base database the shared webshop connects to.') +param webshopSqlDatabase string = 'TailspinToys_Demo_Final' + +var commonTags = { + SecurityControl: 'Ignore' +} + +module vnet 'infra/modules/vnet.bicep' = { + name: 'shared-vnet' + params: { + location: location + envName: envName + tags: commonTags + } +} + +module sqlManagedInstance 'infra/modules/sqlManagedInstance.bicep' = { + name: 'shared-sqlmi' + params: { + location: location + envName: envName + instanceName: 'sqlhackmi-${envName}' + subnetId: vnet.outputs.managedInstanceSubnetId + administratorLogin: sqlAdminLogin + administratorLoginPassword: sqlPassword + tags: commonTags + } +} + +module appService 'infra/modules/appService.bicep' = { + name: 'shared-webshop' + params: { + location: location + envName: envName + subnetId: vnet.outputs.appServiceSubnetId + sqlDatabase: webshopSqlDatabase + sqlServerFqdn: sqlManagedInstance.outputs.fqdn + sqlAdminLogin: sqlAdminLogin + sqlPassword: sqlPassword + tags: commonTags + } +} + +module storageBackups 'infra/modules/storageBackups.bicep' = { + name: 'shared-backups' + params: { + location: location + envName: envName + containerName: 'build' + tags: commonTags + } +} + +module fabricCapacity 'infra/modules/fabricCapacity.bicep' = { + name: 'shared-fabric' + params: { + location: location + envName: envName + skuName: fabricSkuName + administrationMembers: fabricAdminMembers + tags: commonTags + } +} + +output sqlManagedInstanceName string = sqlManagedInstance.outputs.name +output sqlManagedInstanceFqdn string = sqlManagedInstance.outputs.fqdn +output sqlManagedInstanceIdentityPrincipalId string = sqlManagedInstance.outputs.identityPrincipalId +output vnetName string = vnet.outputs.vnetName +output fabricSubnetName string = vnet.outputs.fabricSubnetName +output fabricCapacityName string = fabricCapacity.outputs.capacityName +output backupStorageAccountName string = storageBackups.outputs.storageAccountName +output backupContainerName string = storageBackups.outputs.containerName +output webshopName string = appService.outputs.webAppName +output webshopDefaultHostname string = appService.outputs.defaultHostname diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/InsertProduct.sql b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/InsertProduct.sql new file mode 100644 index 000000000..338a70ffc --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/InsertProduct.sql @@ -0,0 +1,25 @@ +USE [##db-name##] +INSERT INTO [dbo].[Product] + ( + ProductSKU, + ProductName, + ProductCategory, + ItemGroup, + KitType, + Channels, + Demographic, + RetailPrice, + Photo + ) + VALUES + ( + '9000-FABRIC-RANGER', -- ProductSKU + 'Fabric Space Ranger', -- ProductName + 'ActionFigure', -- ProductCategory + 'Toy', -- ItemGroup + 'RTF', -- KitType + 0, -- Channels + 'Child', -- Demographic + 49.99, -- RetailPrice + 0xffd8ffe000104a46494600010100000100010000ffe201d84943435f50524f46494c45000101000001c800000000043000006d6e74725247422058595a2007e00001000100000000000061637370000000000000000000000000000000000000000000000000000000010000f6d6000100000000d32d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000964657363000000f0000000247258595a00000114000000146758595a00000128000000146258595a0000013c00000014777470740000015000000014725452430000016400000028675452430000016400000028625452430000016400000028637072740000018c0000003c6d6c756300000000000000010000000c656e5553000000080000001c007300520047004258595a200000000000006fa2000038f50000039058595a2000000000000062990000b785000018da58595a2000000000000024a000000f840000b6cf58595a20000000000000f6d6000100000000d32d706172610000000000040000000266660000f2a700000d59000013d000000a5b00000000000000006d6c756300000000000000010000000c656e5553000000200000001c0047006f006f0067006c006500200049006e0063002e00200032003000310036ffdb004300 + ); \ No newline at end of file diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/Jobs.sql b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/Jobs.sql new file mode 100644 index 000000000..5acb7613c --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/Jobs.sql @@ -0,0 +1,206 @@ +USE [msdb] +GO + +/****** Job: [2 Fabric Space Ranger Workload] ******/ +-- Rewritten for the MicroHack framework: both job steps now discover the per-attendee +-- databases by name pattern instead of iterating a fixed TailspinToys_User001..100 range. +BEGIN TRANSACTION +DECLARE @ReturnCode INT +SELECT @ReturnCode = 0 +IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) +BEGIN +EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback + +END + +DECLARE @jobId BINARY(16) +EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'2 Fabric Space Ranger Workload', + @enabled=0, + @notify_level_eventlog=0, + @notify_level_email=0, + @notify_level_netsend=0, + @notify_level_page=0, + @delete_level=0, + @description=N'No description available.', + @category_name=N'[Uncategorized (Local)]', + @owner_login_name=N'sa', @job_id = @jobId OUTPUT +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +/****** Step [Insert Space Ranger sales data] ******/ +EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Insert Space Ranger sales data', + @step_id=1, + @cmdexec_success_code=0, + @on_success_action=3, + @on_success_step_id=0, + @on_fail_action=2, + @on_fail_step_id=0, + @retry_attempts=0, + @retry_interval=0, + @os_run_priority=0, @subsystem=N'TSQL', + @command=N' + +-- Insert Space Ranger sales rows into every per-attendee TailspinToys database. + +SET NOCOUNT ON + +DECLARE @SQLCMD_TEMPLATE NVARCHAR(MAX) +DECLARE @SQLCMD NVARCHAR(MAX) +DECLARE @DBNAME NVARCHAR(128) +DECLARE @DBFBNAME NVARCHAR(128) + +SET @SQLCMD_TEMPLATE = '' +DECLARE @NumberOfRows INT = 10; +DECLARE @ProductID INT; + SELECT @ProductID = [ProductID] + FROM [##db-name##].[dbo].[Product] + WHERE [ProductName] = ''''Fabric Space Ranger''''; + +;WITH Numbers AS ( + SELECT 1 AS n + UNION ALL + SELECT n + 1 FROM Numbers WHERE n + 1 <= @NumberOfRows +) +INSERT INTO [##db-name##].[dbo].[Sales] ( + [OrderDate], [ShipDate], [CustomerStateID], [ProductID], [Quantity], + [UnitPrice], [DiscountAmount], [PromotionCode], [CustomerID], [TotalPrice] +) +SELECT + CAST(GETDATE() AS DATE) AS [OrderDate], + NULL AS [ShipDate], + 1 + ABS(CHECKSUM(NEWID())) % 51 AS [CustomerStateID], + @ProductID AS [ProductID], + 1 + ABS(CHECKSUM(NEWID())) % 5 AS [Quantity], + 49.99 AS [UnitPrice], + CASE WHEN ABS(CHECKSUM(NEWID())) % 2 = 1 + THEN ROUND((ABS(CHECKSUM(NEWID())) % 21) * 0.01 * 49.99, 2) + ELSE 0.00 + END AS [DiscountAmount], + CASE WHEN ABS(CHECKSUM(NEWID())) % 2 = 1 + THEN CONCAT(''''FIRSTMISSION'''', REPLACE(STR(ROUND((ABS(CHECKSUM(NEWID())) % 21) * 0.01 * 49.99, 2), 6, 2), ''''.'''', '''''''')) + ELSE NULL + END AS [PromotionCode], + 1 + ABS(CHECKSUM(NEWID())) % 1000 AS [CustomerID], + (1 + ABS(CHECKSUM(NEWID())) % 5) * 49.99 + - CASE WHEN ABS(CHECKSUM(NEWID())) % 2 = 1 + THEN ROUND((ABS(CHECKSUM(NEWID())) % 21) * 0.01 * 49.99, 2) + ELSE 0.00 + END AS [TotalPrice] +FROM Numbers +--OPTION (MAXRECURSION 32767);'' + +DECLARE db_cursor CURSOR LOCAL FAST_FORWARD FOR + SELECT name FROM sys.databases + WHERE name LIKE ''TailspinToys[_]%'' + AND name NOT LIKE ''TailspinToysFeedback[_]%'' + AND name NOT LIKE ''%[_]Demo[_]%'' +OPEN db_cursor +FETCH NEXT FROM db_cursor INTO @DBNAME +WHILE @@FETCH_STATUS = 0 +BEGIN + SET @DBFBNAME = REPLACE(@DBNAME, ''TailspinToys_'', ''TailspinToysFeedback_'') + SET @SQLCMD = REPLACE(REPLACE(@SQLCMD_TEMPLATE,''##db-name##'',@DBNAME),''##db-fb-name##'',@DBFBNAME) + BEGIN TRY + EXECUTE (@SQLCMD) + END TRY + BEGIN CATCH + PRINT ERROR_MESSAGE() + END CATCH + FETCH NEXT FROM db_cursor INTO @DBNAME +END +CLOSE db_cursor +DEALLOCATE db_cursor', + @database_name=N'master', + @flags=8 +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +/****** Step [Insert Space Ranger feedback data] ******/ +EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Insert Space Ranger feedback data', + @step_id=2, + @cmdexec_success_code=0, + @on_success_action=1, + @on_success_step_id=0, + @on_fail_action=2, + @on_fail_step_id=0, + @retry_attempts=0, + @retry_interval=0, + @os_run_priority=0, @subsystem=N'TSQL', + @command=N'-- Insert Space Ranger feedback into every per-attendee TailspinToysFeedback database. + +SET NOCOUNT ON + +DECLARE @SQLCMD_TEMPLATE NVARCHAR(MAX) +DECLARE @SQLCMD NVARCHAR(MAX) +DECLARE @DBNAME NVARCHAR(128) +DECLARE @DBFBNAME NVARCHAR(128) + +SET @SQLCMD_TEMPLATE = '' +DECLARE @CustomerID INT; +DECLARE @Rating INT; +DECLARE @ReviewText NVARCHAR(4000); +DECLARE @ProductID INT +SELECT @ProductID = [ProductID] from [##db-name##].[dbo].[Product] WHERE [ProductName] = ''''Fabric Space Ranger'''' + +SELECT TOP 1 @CustomerID = [CustomerID] +FROM [##db-name##].[dbo].[Sales] +WHERE [ProductID] = @ProductID +ORDER BY [OrderNumber] DESC; + +SET @Rating = 1 + ABS(CHECKSUM(NEWID())) % 5; +SET @ReviewText = CASE @Rating + WHEN 1 THEN N''''Very poor experience. Would not recommend.'''' + WHEN 2 THEN N''''Below average. Product did not meet expectations.'''' + WHEN 3 THEN N''''Average product. Satisfactory but not outstanding.'''' + WHEN 4 THEN N''''Good quality, I am satisfied with my purchase.'''' + WHEN 5 THEN N''''Very satisfied! Exceeded all my expectations. I love the Fabric Space Ranger'''' + END; + +INSERT INTO [##db-fb-name##].[dbo].[ProductFeedback] ([ProductID], [CustomerID], [FeedbackDate], [Rating], [ReviewText]) +VALUES (@ProductID, @CustomerID, GETDATE(), @Rating, @ReviewText)'' + +DECLARE db_cursor CURSOR LOCAL FAST_FORWARD FOR + SELECT name FROM sys.databases + WHERE name LIKE ''TailspinToys[_]%'' + AND name NOT LIKE ''TailspinToysFeedback[_]%'' + AND name NOT LIKE ''%[_]Demo[_]%'' +OPEN db_cursor +FETCH NEXT FROM db_cursor INTO @DBNAME +WHILE @@FETCH_STATUS = 0 +BEGIN + SET @DBFBNAME = REPLACE(@DBNAME, ''TailspinToys_'', ''TailspinToysFeedback_'') + SET @SQLCMD = REPLACE(REPLACE(@SQLCMD_TEMPLATE,''##db-name##'',@DBNAME),''##db-fb-name##'',@DBFBNAME) + BEGIN TRY + EXECUTE (@SQLCMD) + END TRY + BEGIN CATCH + PRINT ERROR_MESSAGE() + END CATCH + FETCH NEXT FROM db_cursor INTO @DBNAME +END +CLOSE db_cursor +DEALLOCATE db_cursor', + @database_name=N'master', + @flags=8 +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'data every minute', + @enabled=1, + @freq_type=4, + @freq_interval=1, + @freq_subday_type=4, + @freq_subday_interval=1, + @freq_relative_interval=0, + @freq_recurrence_factor=0, + @active_start_date=20260219, + @active_end_date=99991231, + @active_start_time=0, + @active_end_time=235959, + @schedule_uid=N'10d16eeb-c871-463b-9572-03d5179cf62a' +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +COMMIT TRANSACTION +GOTO EndSave +QuitWithRollback: + IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION +EndSave: +GO diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/StoredProcedure.sql b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/StoredProcedure.sql new file mode 100644 index 000000000..e29f29a2b --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/sql/StoredProcedure.sql @@ -0,0 +1,96 @@ +-- usp_PurchaseSpaceRanger: shared purchase entry point called by the webshop. +-- Rewritten for the MicroHack framework: instead of a hardcoded TailspinToys_User001..100 +-- loop, it discovers every per-attendee database by name pattern and fans a purchase +-- out to Demo_Final plus each attendee database that currently exists. +USE [TailspinToys_Demo_Final] +GO + +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO + +CREATE OR ALTER PROC [dbo].[usp_PurchaseSpaceRanger] + @CustomerName nvarchar(200), + @Email nvarchar(320), + @Amount INT +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @SQLCMD_TEMPLATE NVARCHAR(MAX) + DECLARE @SQLCMD NVARCHAR(MAX) + DECLARE @DBNAME NVARCHAR(128) + + SET @SQLCMD_TEMPLATE = ' + BEGIN TRAN; + + DECLARE @CustomerId int; + DECLARE @ProductId int; + DECLARE @Price decimal(9,2) = 49.99; + DECLARE @DiscountAmount decimal(9,2) = 10.00; + DECLARE @CustomerStateID int; + + -- Pick product + SELECT TOP (1) + @ProductId = ProductId + FROM [##db-name##].dbo.Product + WHERE ProductName LIKE ''%Fabric Space Ranger%''; + + IF @ProductId IS NULL + THROW 50001, ''Product not found (Fabric Space Ranger).'', 1; + + -- Random state id 1..51 + SET @CustomerStateID = 1 + ABS(CHECKSUM(NEWID())) % 51; + + -- Insert customer + INSERT INTO [##db-name##].dbo.Customer (CustomerName, Email,Country,DateCreated) + VALUES (@CustomerName, @Email,''MicroHack'', GETDATE()); + + SET @CustomerId = SCOPE_IDENTITY(); + + -- Insert sales row + INSERT INTO [##db-name##].dbo.Sales + (OrderDate, ShipDate, CustomerStateID, ProductID, Quantity, UnitPrice, + DiscountAmount, PromotionCode, CustomerID, TotalPrice) + VALUES + (GETDATE(), NULL, + @CustomerStateID, + @ProductId, + @Amount, + @Price, + @DiscountAmount, + ''LaunchDay'', + @CustomerId, + (@Price - @DiscountAmount) * @Amount); + + COMMIT;' + + -- Iterate over Demo_Final plus every per-attendee TailspinToys database. + DECLARE db_cursor CURSOR LOCAL FAST_FORWARD FOR + SELECT name FROM sys.databases + WHERE name = 'TailspinToys_Demo_Final' + OR (name LIKE 'TailspinToys[_]%' + AND name NOT LIKE 'TailspinToysFeedback[_]%' + AND name NOT LIKE '%[_]Demo[_]%') + + OPEN db_cursor + FETCH NEXT FROM db_cursor INTO @DBNAME + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @SQLCMD = REPLACE(@SQLCMD_TEMPLATE,'##db-name##',@DBNAME) + BEGIN TRY + EXECUTE sp_executesql @SQLCMD, + N'@CustomerName NVARCHAR(200), @Email NVARCHAR(320), @Amount INT', + @CustomerName=@CustomerName, @Email=@Email, @Amount=@Amount + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 ROLLBACK; + PRINT ERROR_MESSAGE() + END CATCH + FETCH NEXT FROM db_cursor INTO @DBNAME + END + CLOSE db_cursor + DEALLOCATE db_cursor +END; +GO diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/.gitignore b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/.gitignore new file mode 100644 index 000000000..e8d138cc3 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/.gitignore @@ -0,0 +1,32 @@ +# Node.js application + +# Dependencies +node_modules/ +jspm_packages/ + +# Debug logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Environment variables / secrets +.env +.env.* +!.env.example + +# Build output +dist/ +build/ +coverage/ + +# Caches +.npm +.eslintcache + +# Runtime logs +*.log + +# OS files +.DS_Store +Thumbs.db diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/package-lock.json b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/package-lock.json new file mode 100644 index 000000000..930a8f119 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/package-lock.json @@ -0,0 +1,2787 @@ +{ + "name": "fabric-space-ranger-shop", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fabric-space-ranger-shop", + "version": "1.0.0", + "dependencies": { + "dotenv": "^16.4.5", + "express": "^4.19.2", + "mssql": "^10.0.2" + } + }, + "node_modules/@azure-rest/core-client": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.6.1.tgz", + "integrity": "sha512-KzI10qnkWTsVS2yRBUdc8NLUJ1rOm+292mYs7Pe9wqAj/jv4bRskVm1l8XkKeVTN0OCQtrU5RG0Yhjbz1Wmg7g==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure-rest/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", + "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.24.0.tgz", + "integrity": "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.4.2.tgz", + "integrity": "sha512-0q5DL4uyR0EZ4RXQKD8MadGH6zTIcloUoS/RVbCpNpej4pwte0xpqYxk8K97Py2RiuUvI7F4GXpoT4046VfufA==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.5.0", + "@azure/msal-node": "^2.5.1", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/keyvault-common": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.0.tgz", + "integrity": "sha512-aCDidWuKY06LWQ4x7/8TIXK6iRqTaRWRL3t7T+LC+j1b07HtoIsOxP/tU90G4jCSBn5TAyUTCtA4MS/y5Hudaw==", + "dependencies": { + "@azure-rest/core-client": "^2.3.3", + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.10.0", + "@azure/logger": "^1.1.4", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/keyvault-common/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/keyvault-keys": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.10.2.tgz", + "integrity": "sha512-VmUSLbXRAbSzDD8grXHGPaknYs0SKr3yuf6U+d4XMpX4XuVYskNqbTTwXce0zR1LyxfTZm9rWEBcvs3vdYwCmQ==", + "dependencies": { + "@azure-rest/core-client": "^2.3.3", + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-lro": "^2.7.2", + "@azure/core-paging": "^1.6.2", + "@azure/core-rest-pipeline": "^1.19.0", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/keyvault-common": "^2.1.0", + "@azure/logger": "^1.1.4", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/keyvault-keys/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.30.0.tgz", + "integrity": "sha512-I0XlIGVdM4E9kYP5eTjgW8fgATdzwxJvQ6bm2PNiHaZhEuUz47NYw1xHthC9R+lXz4i9zbShS0VdLyxd7n0GGA==", + "dependencies": { + "@azure/msal-common": "14.16.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.1.tgz", + "integrity": "sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.3.tgz", + "integrity": "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw==", + "dependencies": { + "@azure/msal-common": "14.16.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@js-joda/core": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.7.0.tgz", + "integrity": "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==" + }, + "node_modules/@tediousjs/connection-string": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.5.0.tgz", + "integrity": "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==" + }, + "node_modules/@types/node": { + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/readable-stream": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.6.tgz", + "integrity": "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-aggregate-error": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.14.tgz", + "integrity": "sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA==", + "dependencies": { + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "globalthis": "^1.0.4", + "has-property-descriptors": "^1.0.2", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, + "node_modules/jsbi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz", + "integrity": "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/mssql": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/mssql/-/mssql-10.0.4.tgz", + "integrity": "sha512-MhX5IcJ75/q+dUiOe+1ajpqjEe96ZKqMchYYPUIDU+Btqhwt4gbFeZhcGUZaRCEMV9uF+G8kLvaNSFaEzL9OXQ==", + "dependencies": { + "@tediousjs/connection-string": "^0.5.0", + "commander": "^11.0.0", + "debug": "^4.3.3", + "rfdc": "^1.3.0", + "tarn": "^3.0.2", + "tedious": "^16.4.0" + }, + "bin": { + "mssql": "bin/mssql" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/mssql/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mssql/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tedious": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-16.7.1.tgz", + "integrity": "sha512-NmedZS0NJiTv3CoYnf1FtjxIDUgVYzEmavrc8q2WHRb+lP4deI9BpQfmNnBZZaWusDbP5FVFZCcvzb3xOlNVlQ==", + "dependencies": { + "@azure/identity": "^3.4.1", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.5.3", + "bl": "^6.0.3", + "es-aggregate-error": "^1.0.9", + "iconv-lite": "^0.6.3", + "js-md4": "^0.3.2", + "jsbi": "^4.3.0", + "native-duplexpair": "^1.0.0", + "node-abort-controller": "^3.1.1", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tedious/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + } +} diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/package.json b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/package.json new file mode 100644 index 000000000..33e071876 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/package.json @@ -0,0 +1,18 @@ +{ + "name": "fabric-space-ranger-shop", + "version": "1.0.0", + "description": "SQL Konferenz 2026 demo web app: insert sales data into Azure SQL MI", + "type": "module", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "overrides": { + "hasown": "2.0.3" + }, + "dependencies": { + "dotenv": "^16.4.5", + "express": "^4.19.2", + "mssql": "^10.0.2" + } +} diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/images/SpaceRanger.png b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/images/SpaceRanger.png new file mode 100644 index 000000000..d2583352e Binary files /dev/null and b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/images/SpaceRanger.png differ diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/index.html b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/index.html new file mode 100644 index 000000000..41f392782 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/index.html @@ -0,0 +1,172 @@ + + + + + + Fabric Space Ranger — SQL Konferenz 2026 + + + + +
+
+ +
+

Order the Fabric Space Ranger

+

Limited MicroHack day drop

+ +
+
+ Fabric Space Ranger +
+
+
Fabric Space Ranger
+
+
✅ MicroHack exclusive
+
⚡ Ships to the universe*
+
🔒 Secure form submission
+
*Simulation only 😉
+
+
+
+
+ + + + +
+
+ + + + + \ No newline at end of file diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/styles.css b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/styles.css new file mode 100644 index 000000000..f9064a00c --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/public/styles.css @@ -0,0 +1,579 @@ +:root{ + --bg0:#070A12; + --bg1:#0B1220; + --card: rgba(255,255,255,.06); + --card2: rgba(255,255,255,.08); + --border: rgba(255,255,255,.12); + + --text:#ECF2FF; + --muted:#A9B4D6; + --muted2:#7E8AB2; + + --accent:#7C5CFF; + --accent2:#4F46E5; + + --success:#2DD4BF; + --danger:#FF5C7A; + --info:#60A5FA; + + --shadow: 0 18px 60px rgba(0,0,0,.45); + --shadow2: 0 10px 30px rgba(0,0,0,.35); + + --radius: 18px; + --radius2: 14px; + + --container: 1120px; +} + +*{ box-sizing:border-box; } +html,body{ height:100%; } +body{ + margin:0; + font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji","Segoe UI Emoji"; + color:var(--text); + background: + radial-gradient(1100px 700px at 15% -10%, rgba(124,92,255,.35), transparent 55%), + radial-gradient(900px 600px at 90% 10%, rgba(96,165,250,.22), transparent 55%), + radial-gradient(900px 700px at 50% 120%, rgba(45,212,191,.12), transparent 55%), + linear-gradient(180deg, var(--bg0), var(--bg1)); +} + +/* Layout */ +.container{ + width: min(var(--container), calc(100% - 48px)); + margin: 0 auto; +} + +.page{ + padding: 34px 0 46px; +} + +.topbar{ + position: sticky; + top: 0; + z-index: 30; + backdrop-filter: blur(10px); + background: rgba(7,10,18,.65); + border-bottom: 1px solid rgba(255,255,255,.08); +} + +.topbar__inner{ + display:flex; + align-items:center; + justify-content:space-between; + gap: 16px; + padding: 14px 0; +} + +.brand{ + display:flex; + align-items:baseline; + gap: 10px; + text-decoration:none; + color: var(--text); +} + +.brand__mark{ + width: 28px; + height: 28px; + display:grid; + place-items:center; + border-radius: 10px; + background: linear-gradient(135deg, rgba(124,92,255,.95), rgba(79,70,229,.95)); + box-shadow: 0 10px 30px rgba(124,92,255,.22); + font-weight: 900; +} + +.brand__name{ + font-weight: 800; + letter-spacing: .2px; +} + +.brand__tag{ + color: var(--muted2); + font-size: .9rem; + margin-left: 6px; +} + +.nav{ + display:flex; + gap: 14px; +} + +.nav a{ + color: var(--muted); + text-decoration:none; + font-weight: 650; + padding: 8px 10px; + border-radius: 10px; + transition: background .15s ease, color .15s ease; +} + +.nav a:hover{ + color: var(--text); + background: rgba(255,255,255,.06); +} + +.hero{ + display:grid; + grid-template-columns: 1.2fr .8fr; + gap: 22px; + align-items:start; +} + +@media (max-width: 980px){ + .hero{ grid-template-columns: 1fr; } + .nav{ display:none; } +} + +.hero__left{ + display:flex; + flex-direction:column; + gap: 14px; +} + +.badge{ + display:inline-flex; + align-items:center; + gap: 10px; + padding: 10px 12px; + border-radius: 999px; + background: rgba(255,255,255,.06); + border: 1px solid rgba(255,255,255,.10); + color: var(--muted); + width: fit-content; +} + +.badge__dot{ + width: 10px; + height: 10px; + border-radius: 999px; + background: var(--accent); + box-shadow: 0 0 0 4px rgba(124,92,255,.18); +} + +.hero__title{ + margin: 0; + font-size: clamp(2.0rem, 3.2vw, 3.0rem); + line-height: 1.08; + letter-spacing: -0.6px; +} + +.accent{ color: #b8abff; } + +.hero__sub{ + margin: 0; + color: var(--muted); + font-size: 1.05rem; + max-width: 64ch; +} + +.hero__highlights{ + display:flex; + flex-wrap:wrap; + gap: 10px; +} + +.pill{ + padding: 10px 12px; + border-radius: 999px; + background: rgba(255,255,255,.05); + border: 1px solid rgba(255,255,255,.09); + color: var(--muted); + font-weight: 650; +} + +/* Product card */ +.productCard{ + display:grid; + grid-template-columns: 0.9fr 1.1fr; + gap: 18px; + padding: 18px; + border-radius: var(--radius); + background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.04)); + border: 1px solid rgba(255,255,255,.10); + box-shadow: var(--shadow2); +} + +@media (max-width: 720px){ + .productCard{ grid-template-columns: 1fr; } +} + +.productMedia{ + border-radius: var(--radius2); + background: + radial-gradient(320px 220px at 50% 30%, rgba(124,92,255,.25), transparent 60%), + radial-gradient(280px 220px at 70% 90%, rgba(96,165,250,.18), transparent 60%), + rgba(255,255,255,.03); + border: 1px solid rgba(255,255,255,.08); + padding: 14px; + display:grid; + place-items:center; + min-height: 240px; +} + +.productImage{ + width: min(320px, 100%); + height: auto; + filter: drop-shadow(0 20px 25px rgba(0,0,0,.35)); +} + +.productInfo{ display:flex; flex-direction:column; gap: 10px; } +.productMeta{ display:flex; flex-direction:column; gap: 4px; } + +.productName{ + font-weight: 900; + letter-spacing: -0.3px; + font-size: 1.35rem; +} + +.productSku{ + color: var(--muted2); + font-size: .95rem; +} + +.priceRow{ + display:flex; + align-items:center; + justify-content:space-between; + gap: 12px; + margin-top: 4px; +} + +.price{ + display:flex; + align-items:baseline; + gap: 10px; +} +.price__now{ + font-weight: 900; + font-size: 1.55rem; +} +.price__was{ + color: var(--muted2); + text-decoration: line-through; + font-weight: 700; +} + +.saveTag{ + padding: 8px 10px; + border-radius: 999px; + background: rgba(124,92,255,.16); + border: 1px solid rgba(124,92,255,.28); + color: #cfc7ff; + font-weight: 850; + letter-spacing: .3px; +} + +.productDesc{ + margin: 0; + color: var(--muted); + line-height: 1.45; +} + +.trust{ + display:grid; + gap: 8px; + margin-top: 4px; +} +.trustItem{ + display:flex; + align-items:center; + gap: 10px; + padding: 10px 12px; + border-radius: 12px; + /* make the panel a bit more solid so the page gradient doesn't bleed through */ + background: rgba(255,255,255,.12); + border: 1px solid rgba(255,255,255,.10); + /* text should be clear and legible against the dark card */ + color: var(--text); + font-weight: 650; +} +.trustNote{ + color: var(--muted2); + font-size: .92rem; + margin-left: 4px; +} + +/* Checkout */ +.checkout{ position: relative; } +.checkout__card{ + position: sticky; + top: 78px; + border-radius: var(--radius); + padding: 18px; + background: rgba(255,255,255,.06); + border: 1px solid rgba(124,92,255,.22); + box-shadow: var(--shadow); +} + +@media (max-width: 980px){ + .checkout__card{ position: relative; top: 0; } +} + +.checkout__head{ + padding-bottom: 10px; + border-bottom: 1px solid rgba(255,255,255,.08); + margin-bottom: 14px; +} + +.checkout__title{ + margin: 0; + font-size: 1.25rem; + letter-spacing: -0.2px; +} +.checkout__hint{ + margin: 6px 0 0; + color: var(--muted); + font-size: .98rem; +} + +/* Form */ +.form{ display:grid; gap: 12px; } +.field{ display:grid; gap: 6px; } + +label{ + color: var(--muted); + font-weight: 700; + font-size: .95rem; +} + +input{ + padding: 12px 12px; + border-radius: 14px; + border: 1px solid rgba(255,255,255,.45); + background: rgba(255,255,255,.25); /* lighter */ + color: var(--text); + outline: none; + font-size: 1rem; + width: 100%; /* responsive by default */ +} + +/* ensure inputs shrink on small screens and text remains legible */ +@media (max-width: 600px){ + input{ + font-size: 1.1rem; + padding: 14px 14px; + } +} + +input:focus{ + border-color: rgba(124,92,255,.85); + box-shadow: 0 0 0 4px rgba(124,92,255,.16); +} + +.helper{ + color: var(--muted2); + font-size: .9rem; +} + +/* Quantity */ +.qty{ + display:grid; + grid-template-columns: 44px 1fr 44px; + gap: 8px; + align-items:center; +} + +.qty__btn{ + height: 44px; + border-radius: 14px; + border: 1px solid rgba(255,255,255,.12); + background: rgba(255,255,255,.05); + color: var(--text); + font-size: 1.25rem; + font-weight: 900; + cursor: pointer; + transition: transform .06s ease, background .15s ease; +} + +.qty__btn:hover{ background: rgba(255,255,255,.08); } +.qty__btn:active{ transform: translateY(1px); } + +/* Summary */ +.summary{ + margin-top: 2px; + padding: 12px; + border-radius: 16px; + border: 1px solid rgba(255,255,255,.10); + background: rgba(255,255,255,.04); + display:grid; + gap: 8px; +} + +.summaryRow{ + display:flex; + justify-content:space-between; + gap: 12px; + color: var(--muted); +} + +.summaryRow strong{ color: var(--text); } +.summaryRow .discount{ color: #ffb2c0; } + +.divider{ + height: 1px; + background: rgba(255,255,255,.10); + margin: 2px 0; +} + +.total{ + font-size: 1.05rem; +} +.total strong{ + font-size: 1.15rem; +} + +/* CTA */ +.cta{ + margin-top: 4px; + padding: 12px 14px; + border: none; + border-radius: 16px; + background: linear-gradient(135deg, var(--accent), var(--accent2)); + color: white; + font-weight: 900; + letter-spacing: .2px; + cursor: pointer; + display:flex; + flex-direction:column; + align-items:center; + gap: 2px; + box-shadow: 0 18px 40px rgba(124,92,255,.25); +} + +.cta__sub{ + font-weight: 700; + font-size: .9rem; + opacity: .92; +} + +.cta:disabled{ + opacity: .65; + cursor: not-allowed; +} + +/* Status */ +.status{ + margin-top: 10px; + padding: 10px 12px; + border-radius: 14px; + min-height: 22px; + color: var(--muted); + border: 1px dashed rgba(255,255,255,.16); + background: rgba(7,10,18,.25); +} + +.status.success{ + border-color: rgba(45, 212, 191, 0.55); + color: var(--success); +} + +.status.error{ + border-color: rgba(255, 92, 122, 0.65); + color: var(--danger); +} + +.status.info{ + border-color: rgba(96, 165, 250, 0.60); + color: var(--info); +} + +.fineprint{ + margin: 2px 0 0; + color: var(--muted2); + font-size: .92rem; + line-height: 1.35; +} + +/* Mini FAQ */ +.miniFaq{ + margin-top: 14px; + padding: 14px 14px; + border-radius: var(--radius); + background: rgba(255,255,255,.04); + border: 1px solid rgba(255,255,255,.10); +} + +.miniFaq__title{ + font-weight: 900; + margin-bottom: 8px; + letter-spacing: -0.2px; +} + +details{ + padding: 10px 10px; + border-radius: 12px; + border: 1px solid rgba(255,255,255,.08); + background: rgba(255,255,255,.03); + margin-top: 10px; +} + +summary{ + cursor: pointer; + font-weight: 800; + color: var(--text); +} + +details p{ + margin: 8px 0 0; + color: var(--muted); + line-height: 1.45; +} + +code{ + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: .95em; + padding: 2px 6px; + border-radius: 8px; + background: rgba(255,255,255,.06); + border: 1px solid rgba(255,255,255,.10); +} + +/* Value strip */ +.strip{ + margin-top: 22px; + display:grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +@media (max-width: 980px){ + .strip{ grid-template-columns: 1fr; } +} + +.strip__item{ + padding: 16px; + border-radius: var(--radius); + background: rgba(255,255,255,.04); + border: 1px solid rgba(255,255,255,.10); +} + +.strip__kpi{ + font-weight: 950; + font-size: 1.1rem; + letter-spacing: -0.2px; +} +.strip__label{ + margin-top: 6px; + color: var(--muted); +} + +/* Footer */ +.footer{ + border-top: 1px solid rgba(255,255,255,.08); + background: rgba(7,10,18,.55); +} + +.footer__inner{ + display:flex; + justify-content:space-between; + gap: 12px; + padding: 18px 0; +} + +@media (max-width: 720px){ + .footer__inner{ flex-direction:column; } +} + +.muted{ color: var(--muted); } \ No newline at end of file diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/server.js b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/server.js new file mode 100644 index 000000000..6cd79acc7 --- /dev/null +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/labautomation/src/server.js @@ -0,0 +1,96 @@ +import express from "express"; +import sql from "mssql"; + +const app = express(); +app.use(express.json()); +app.use(express.static("public")); + +const port = process.env.PORT || 3000; + +// --- SQL config (SQL Auth) --- +function getSqlConfig() { + const server = process.env.SQL_SERVER; // e.g. ,3342 (if using MI public endpoint) + const database = process.env.SQL_DATABASE; // your DB name + const user = process.env.SQL_USER; // SQL login + const password = process.env.SQL_PASSWORD; // SQL password + + if (!server || !database || !user || !password) { + throw new Error("Missing SQL env vars. Set SQL_SERVER, SQL_DATABASE, SQL_USER, SQL_PASSWORD."); + } + + return { + server, + database, + user, + password, + options: { + encrypt: true, + trustServerCertificate: false + }, + pool: { + max: 10, + min: 0, + idleTimeoutMillis: 30000 + } + }; +} + +let poolPromise = null; +async function getPool() { + if (!poolPromise) { + const cfg = getSqlConfig(); + poolPromise = sql.connect(cfg); + } + return poolPromise; +} + +app.get("/health", (_req, res) => res.json({ ok: true })); + +app.post("/api/purchase", async (req, res) => { + try { + const customerName = String(req.body.customerName ?? "").trim(); + const email = String(req.body.email ?? "").trim(); + const amountRaw = req.body.amount; + + // Basic validation + if (!customerName || customerName.length > 200) { + return res.status(400).json({ ok: false, message: "CustomerName is required (max 200 chars)." }); + } + if (!email || email.length > 320 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + return res.status(400).json({ ok: false, message: "Valid Email is required (max 320 chars)." }); + } + const amount = Number.parseInt(amountRaw, 10); + if (!Number.isFinite(amount) || amount <= 0) { + return res.status(400).json({ ok: false, message: "Amount must be a positive integer." }); + } + + const pool = await getPool(); + const request = pool.request(); + + // Call stored procedure + const result = await request + .input("CustomerName", sql.NVarChar(200), customerName) + .input("Email", sql.NVarChar(320), email) + .input("Amount", sql.Int, amount) + .execute("dbo.usp_PurchaseSpaceRanger"); + + // If your proc returns anything, we pass it through; otherwise just OK. + const recordset = result?.recordset ?? null; + + res.json({ + ok: true, + message: "Order submitted successfully!", + data: recordset + }); + } catch (err) { + console.error("Purchase error:", err); + res.status(500).json({ + ok: false, + message: "Server error while submitting the order." + }); + } +}); + +app.listen(port, () => { + console.log(`Fabric Space Ranger app running on port ${port}`); +}); \ No newline at end of file diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-01/solution-01.md b/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-01/solution-01.md index d919962ce..70accfdb2 100644 --- a/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-01/solution-01.md +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-01/solution-01.md @@ -19,7 +19,7 @@ [Summary](#summary) # Data Architecture -![generated](../../Images/image002.png) +![generated](../../images/image002.png) # Generic Migration Content | **Narrative** | **Notes** | @@ -51,77 +51,77 @@ In this task, you will enable managed identity authentication on your Azure SQL - + In your workspace, select New item (2). In the New item window, search for Mirror (3) to display all mirroring options and Click Mirrored Azure SQL Managed Instance (4) to start creating a mirrored database from your SQL Managed Instance. - + In the Choose a database connection to get started window, select Azure SQL Managed Instance as the data source. Confirm that Azure SQL Managed Instance appears under New sources, then proceed to configure the connection. - + In the Server (1) field, paste the Azure SQL Managed Instance server name:sqlhackmi-z5v5uebsfrojm.8b4846304eec.database.windows.net so that Microsoft Fabric knows exactly which database to connect to for mirroring. This step directs Fabric to the correct SQL Managed Instance, ensuring secure authentication and accurate data synchronization from your operational system to the analytics environment. Copy and paste directly. - + In the Database (2) field, enter the source database name: TailspinToys_User### Replace ### with your  ttyd user postfix - + Select a Data gateway (3) after entering the server and database to enable secure data transfer between your on-premises or network-restricted Azure SQL Managed Instance and Microsoft Fabric. The data gateway acts as a bridge, allowing Fabric to access and replicate data from the source database while maintaining network security and compliance requirements. - + For Authentication Kind (4), select Basic. In Username (5), enter: demouser. In Password (6), enter: Demo@pass1234567. Username and password can be copied and pasted directly. - + Ensure Use encrypted connection is checked to protect your data during transfer between Azure SQL Managed Instance and Microsoft Fabric. Click Connect (7) to validate the connection and continue. This may take a few minutes to complete. - + Once you are successfully connected, on the Choose data screen, review the list of available tables. Select the tables you want to replicate (or select Select all if required for the lab). Click Connect to proceed. This may take a few minutes to connect. - + In the Destination screen, review the Name of the mirrored database. Verify that Azure SQL Database Managed Instance is shown as the source. Click Create mirrored database to start the mirroring process. - + Please remain on this page and avoid refreshing while the system completes the database mirroring process. This may take a few minutes to complete. - + @@ -143,7 +143,7 @@ In this task, you will initiate the mirroring process between your Azure SQL Man After 2-5 minutes, select Monitor Replication to see the replication status. - + Replicating Status:
Running: Replication is currently running, bringing snapshot and change data into OneLake.
Running with warning: Replication is running with transient errors.
Stopping/Stopped: Replication is stopped.
Error: Fatal error in replication that can't be recovered. @@ -151,7 +151,7 @@ In this task, you will initiate the mirroring process between your Azure SQL Man - + @@ -171,42 +171,42 @@ In this task, you will repeat the setup and monitoring procedures for additional - + In the Choose a database connection to get started window, select Azure SQL Managed Instance as the data source. Confirm that Azure SQL Managed Instance appears under New sources, then proceed to configure the connection. - + In the Server (1) field, paste the Azure SQL Managed Instance server name: sqlhackmi-z5v5uebsfrojm.8b4846304eec.database.windows.net - + In the Database (2) field, enter the source database name: TailspinToysFeedback_User### Replace ### with your  ttyd user postfix - + Select existing Data gateway (3) - + For Authentication kind (4) , select Basic. In the Username (5) field, enter the SQL login username: demouser and in the Password (6) field, enter the corresponding password: Demo@pass1234567.
Ensure Use encrypted connection is checked. Click Connect to validate the connection and continue. - + @@ -219,7 +219,7 @@ In this task, you will repeat the setup and monitoring procedures for additional This happens when a connection with the same name already exists in your workspace. - + @@ -227,14 +227,14 @@ In this task, you will repeat the setup and monitoring procedures for additional This may take a few minutes to complete. - + In the Destination screen, review the Name of the mirrored database. Verify that Azure SQL Database Managed Instance is shown as the source. Click Create mirrored database to start the mirroring process. - + @@ -250,21 +250,21 @@ In this task, you will integrate operational data mirrored from Azure SQL Manage NarrativeNotes Recap: You mirrored two databases in Challenge 1.
You need to create a Lakehouse after mirroring two databases so you can combine and centralize data from multiple sources. Open your Workspace and Click New Item (1). In the search bar, search for Lakehouse (2) and select the Lakehouse (3). - + You need to enter lakehouse name and create it.Example Name: TailspinToysAnalytics and Leave Lakehouse schemas enabled. - + Open your new Lakehouse. Within the Tables folder navigate to dbo schema. By clicking the ellipsis (...) create shortcuts to the required tables (mirrored databases). Select New table shortcut - + In the New shortcut window, under Internal sources, select Microsoft OneLake.This option allows you to create shortcuts from mirrored databases stored in OneLake. - + In the Select a data source type screen, locate the mirrored database TailspinToys_User###. Select TailspinToys_User### created in Challenge 1. - + In the new shortcut screen, expand tables → dbo. Select alle tables except zzVersion (1). Click Next (2) to proceed.Important: Do NOT select zzVersion (this table is not required). - + Review the Summary screen and confirm the selected tables. Verify that the shortcut location is your current Lakehouse. Click Create to create the shortcuts. - + In the Explorer pane, confirm that the tables from TailspinToys_User### now appear under Tables. This confirms that the shortcuts were created successfully. - + @@ -281,37 +281,37 @@ In this task, you will integrate operational data mirrored from Azure SQL Manage This time select the mirrored database TailspinToysFeedback_User###. Click Next to continue. - + Expand Tables → dbo. Select all tables except Customer (1). Click Next (2) to proceed.Important: Do NOT select the Customer table, as it already exists from the first database. - + Review the Summary screen and confirm the selected tables. Verify the shortcut destination is the same Lakehouse. Click Create to finalize the shortcuts. - + Next you create a Shortcut from a file. Therefore click the ellipsis (...) next to the files folder in the explorer pane. This let's you easily integrate external data, such as CSV files, alongside your mirrored databases within the same Lakehouse for centralized analysis. - + Select New shortcut (2) to start creating a shortcut to an external data source. - + In the New shortcut window, choose the source type and Under External sources, select Azure Data Lake Storage Gen2 so you can directly link and access files stored in your organization's data lake, enabling seamless integration of external datasets with your Lakehouse environment for unified analytics. - + Select New connection and Paste DataLake Storage URL: https://employeedata####.dfs.core.windows.net/ (1). Replace #### in the URL with the current day and month in DDMM format (for example, 1206 for June 12). Click Next (2) to continue.This ensures you are establishing a secure and direct connection to your organization’s Data Lake for accessing external files. - + Select the folder with your user number (1). Click Next (2) to continue.Selecting your user-specific folder ensures you only access and work with the files intended for your lab activities. - + Review the selected folder (1) and Click Skip(2) as you do not need to apply any transformation before creating the shortcut. - + Click Create to create the shortcut in your Lakehouse. - + Check shortcut creationIf the shortcut does not appear immediately, click the three dots next to Files and select Refresh. - + Click container### and verify that the CSV file is visible in this container.Replace ### with your user number prefix/postfix. - + Expand the Files folder. Click the Shortcut folder with your user name. Locate the CSV file and click the three dots (⋯) (e.g. employees_user_data.csv). - + Select Load to Tables (1) from the context menu and Choose New table (2). - + Change the table name to employees (1). Set the separator to , (comma) (2). Review the settings, then click Load (3).CSV files use commas to separate values, so selecting a comma ensures data loads into the correct columns. - + In the Explorer pane, locate Tables. Click the three dots (⋯) next to Tables (1). Select Refresh (2). Confirm that a table named employees appears under Tables (3).This confirms that the CSV data has been successfully converted into a Lakehouse table. - + ## Summary diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-02/solution-02.md b/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-02/solution-02.md index 42207e73d..333dbc60a 100644 --- a/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-02/solution-02.md +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-02/solution-02.md @@ -37,7 +37,7 @@ In this step, you activate the required Power BI / Fabric trial so you can use r NarrativeNotes Click your profile image (1), start the Fabric Free Trial (2), select Sweden Central as the trial region (3) to align with your Lakehouse and databases, and confirm activation (4).You need a Power BI license to create and use AI-driven reports directly in Microsoft Fabric. - + ## 2. Create a Semantic Model from the Lakehouse @@ -51,21 +51,21 @@ In this task, you will create a semantic model from the Lakehouse tables. A sema NarrativeNotes In the Lakehouse view, select New semantic model from the top menu. - + In the New semantic model dialog: enter the semantic model name (1), click Select all (2) to include all available tables, and click Confirm (3).Example semantic model name: TalkToYourDataSemanticModel. - + Before clicking Confirm, make sure newly created tables (for example employees) is included in the selection.Use your screenshot below to verify that the table is selected. - + If you cannot see the employees table, switch to the SQL Analytics Endpoint view, expand dbo, and refresh the schema. Then return to the Lakehouse view and verify that the employees table is available. - + Click on Confirm. - + Wait until the semantic model is successfully created. You will then see it listed in the workspace.If creation fails, switch once to the SQL Analytics Endpoint view and create it from there. - + Navigate back to the workspace by clicking the workspace icon in the left navigation. - + Verify that the semantic model was created successfully. - + ## 3. Create the Relationships @@ -79,29 +79,29 @@ In this task, you will optimize the semantic model by creating the required rela NarrativeNotes Confirm the semantic model is in Edit mode before optimization work.Relationship and metadata changes are only available in Editing mode. - + Click Manage relationships from the ribbon while in Editing mode.This opens the relationship manager for creating and adjusting joins. - + In the Manage relationships dialog, click New relationship.Start defining core fact-to-dimension joins. - + Create Sales[CustomerID] -> Customer[CustomerID] and click Save.Enables customer-level analysis of sales. - + Create Sales[OrderDate] -> DimDate[Date].Required for month, quarter, and year reporting. - + Create ProductFeedback[ProductID] -> Product[ProductID].Connects feedback records to the product dimension. - + Create State[RegionID] -> Region[RegionID].Builds the state-to-region geography hierarchy. - + Create Sales[CustomerStateID] -> State[StateID].Maps sales to customer location. - + Create Sales[ProductID] -> Product[ProductID].This is the core product join for sales analytics. - + Create employees[SalesOfficeID] -> SalesOffice[SalesOfficeID].Enables organizational analysis by office and region. Fabric may auto-select Email first because it is a common column, so make sure you choose SalesOfficeID before saving the relationship. - + Review all listed relationships and confirm they are Active, then close the dialog.Quick validation to ensure no required join is missing. - + Verify the model diagram now shows connected relationship lines.The model should now reflect the intended star / multi-fact shape. - + In this semantic model, you use a multi-fact model, which means the semantic model contains more than one fact table, where each fact table represents a different business process. This approach is common when multiple types of business activities need to be analyzed together while still sharing common dimensions. @@ -122,7 +122,7 @@ In this semantic model, you use a multi-fact model, which means the seman - +
    @@ -131,13 +131,13 @@ In this semantic model, you use a multi-fact model, which means the seman
  • The relationship between ProductFeedback and Product now supports bidirectional filtering, enabling combined analysis of product attributes and customer feedback.
- + Apply the same between the Sales table and the Product table by changing Cross-filter direction the value to Both (1). This allows filters to flow in both directions. Click Save (2) to apply the updated relationship settings. Sales metrics can now be analyzed together with product attributes using bidirectional filtering. - + Recheck the model diagram after cross-filter updates.Confirm relationship behavior is stable before metadata edits. - + Switch from Editing back to Viewing mode after optimization is complete. - + Congratulations! Your data model is now ready to use! @@ -153,11 +153,11 @@ In this step, use the semantic model to automatically generate the first report NarrativeNotes Navigate back to the workspace by clicking the workspace icon in the left navigation. - + Open the semantic model and select Explore from the top menu, then click Auto-create a report.This generates a first draft of the report automatically based on the available fields and data patterns. - + Review the generated Quick summary page and check the suggested visuals.You can also adjust the field selection in the Your data pane on the right if needed. - + ## 5. Refine the Report with Prompts @@ -171,9 +171,9 @@ In this step, use prompts or Copilot suggestions to improve the auto-generated r NarrativeNotes Switch to Edit mode from the report menu and confirm with Continue.Use this mode to manually refine and optimize the generated report. - + First click the Copilot icon (1). Then a sidebar opens to the left where you can add your prompt (2) to add something on the report page, like: Provide a detailed, insight-focused overview of "Sum of Quantity by RegionName" visual's data.
Use Copilot prompts to refine the auto-generated report with better visuals, clearer wording, or additional insights.This screenshot shows an example of how Power BI Copilot can help improve the report. - + ## 6. Manually Fine-Tune the Report @@ -188,7 +188,7 @@ If you prefer you can also manually adjust visuals, formatting, and layout, and NarrativeNotes Now try yourself editing the report!
When you are done, click Save, enter a report name, and confirm.This stores the generated and refined report in your workspace. - + ## 7. Prepare the Data for AI @@ -202,23 +202,23 @@ In this task, you will simplify the data schema and prepare the model for AI and NarrativeNotes Open your workspace and open the semantic model in Microsoft Fabric. - + From the top menu, select Prep data for AI. - + In the Prep data for AI dialog, review and configure the steps: simplify the data schema, verified answers, and AI instructions. - + Select Simplify the data schema from the left navigation and expand the semantic model to view all tables and columns.This step keeps only the relevant tables and columns available to AI, improving query quality and response accuracy. - + Review the table selections carefully.Customer: uncheck CustomerID and DateCreated
DimDate: keep all columns checked
Employees: uncheck EmployeeID and SalesOfficeID
FeedbackMedia and FeedbackVote: uncheck all columns - + Continue simplifying the model based on business relevance.Product: uncheck ProductID
ProductFeedback: uncheck CustomerID, FeedbackID, and ProductID
Region: uncheck RegionID - + Review the remaining fact and dimension tables.Sales: uncheck CustomerID, CustomerStateID, OrderDate, ProductID, and ShipDate
SalesOffice: uncheck SalesOfficeID and StateID - + Finalize the schema cleanup.State: uncheck RegionID and StateID - + After reviewing your selections, click Apply. - + ## 8. Optional: Add Verified Answers @@ -232,7 +232,7 @@ Verified answers are optional and are created from Power BI reports, not NarrativeNotes In the left navigation, select Verified answers (preview).No configuration is required at this step. Leave this section unchanged and continue. - + ## 9. Set Up AI Instructions @@ -246,9 +246,9 @@ In this task, you will add business-focused AI instructions so Copilot and other NarrativeNotes In the left navigation, select Add AI instructions (preview). In the text box, add clear, business-focused instructions, then click Apply. - + See the example section below for a sample instruction set. - +
Example of AI Instructions (click to expand)
You help users explore and understand this semantic model by answering questions using the correct business logic, relationships, and terminology. Provide clear, concise insights without exposing SQL unless explicitly requested. @@ -297,11 +297,11 @@ In this step, open Power BI Copilot from the report and ask natural language que NarrativeNotes Navigate back to the workspace by clicking the workspace icon in the left navigation. - + Open the previously created Report. - + Click Copilot in the report and select Get started.You can now ask natural language questions directly against the prepared report and semantic model. - + Try asking these sample questions:
  • "What is this report about?"
  • diff --git a/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-03/solution-03.md b/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-03/solution-03.md index 4bf6abe26..d90fe6d0c 100644 --- a/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-03/solution-03.md +++ b/03-Azure/01-02 Data/03-Talk_to_your_data/walkthrough/challenge-03/solution-03.md @@ -38,15 +38,15 @@ In this task, you will set up a Data Agent to enable intelligent data interactio NarrativeNotes Click New item (Top Left). Search Data agent in search bar (1) and select Data agent(2). - + In the Create data agent dialog: Enter a name for the agent (1)and click Create(2) Example Name: SpaceRangerAgent - + On the Set up your data agent screen, click Add data source. - + Select the Lakehouse by clicking the checkbox next to it. Click Add to attach the Lakehouse to the Data Agent. - + In the Explorer pane on the left, confirm the Lakehouse is listed as an data sourceLeave the Page open, you will work with this agent in the next chapter. - + Expand the Lakehouse node in the Explorer pane and verify that all required tables are visible and Ensure that all tables are checked and accessible. The Data Agent requires access to these tables to function properly.If any tables are not visible, refresh the Lakehouse connection or check that the tables were successfully created in Challenge 1. Without proper table access, the Data Agent cannot answer questions accurately. @@ -96,7 +96,7 @@ Consider role and purpose/tone, structure, and verbosity/Language/Rules for unce | **Narrative** | **Screenshot** | |:------------|:--------------| -|Agent Instruction|![](../../Images/image074.png)| +|Agent Instruction|![](../../images/image074.png)| ## 4.2. Data source description and instructions Consider the data schema. @@ -105,8 +105,8 @@ Consider the data schema. | **Narrative** | **Screenshot** | |:------------|:--------------| -|Data Source Description|![](../../Images/image073.png)| -|Data Source Instruction(Box bottom)|![](../../Images/image075.png)| +|Data Source Description|![](../../images/image073.png)| +|Data Source Instruction(Box bottom)|![](../../images/image075.png)| # 5. Compare our agent instructions and data source guidance with yours In this task, you will see how answer quality improves based on how clearly you define the agent instructions and the data source description/instructions. @@ -123,10 +123,10 @@ Identify the months with the highest toy sales. Then analyze whether those month | Agent Instructions | Data Source Description and Instructions | |:--|:--| -|

    Default state:
    - No agent instruction text is provided.
    - The agent must infer intent on its own. |

    Default state:
    - No data source description or instruction text is provided.
    - The agent has no routing context for the data. | +|

    Default state:
    - No agent instruction text is provided.
    - The agent must infer intent on its own. |

    Default state:
    - No data source description or instruction text is provided.
    - The agent has no routing context for the data. | **Data Agent Answer** - + What to notice: - The agent reports no matching data for the request. - It cannot provide month-by-month sales and satisfaction analysis. @@ -136,10 +136,10 @@ What to notice: | Agent Instructions | Data Source Description and Instructions | |:--|:--| -|

    Text DetailsYou are a Microsoft Fabric Data Agent.
    Answer questions using the connected data.

    Included in this step:
    - A basic role is introduced.

    |

    Text DetailsData Source Description
    The dataset contains sales, products, customers, and dates.
    Data Source Instructions
    Use the dataset to answer questions.

    Included in this step:
    - A minimal data source description and instructions are added.
    | +|

    Text DetailsYou are a Microsoft Fabric Data Agent.
    Answer questions using the connected data.

    Included in this step:
    - A basic role is introduced.

    |

    Text DetailsData Source Description
    The dataset contains sales, products, customers, and dates.
    Data Source Instructions
    Use the dataset to answer questions.

    Included in this step:
    - A minimal data source description and instructions are added.
    | **Data Agent Answer** - + What to notice: - The agent now returns top sales months with numeric values. - The satisfaction analysis is still inconsistent. @@ -149,10 +149,10 @@ What to notice: | Agent Instructions | Data Source Description and Instructions | |:--|:--| -|

    Text DetailsYou are a Microsoft Fabric Data Agent.
    Answer business questions using only the connected tables.

    Consider the data schema:
    - Sales contains transactional data.
    - Product represents toys.
    - Customer contains customer attributes.
    - DimDate must be used for all time-based filtering.

    Avoid guessing when data is not available.

    Included in this step:
    - Role and scope are enforced for connected tables only.
    - Schema-aware guidance is added (Sales, Product, Customer, DimDate).
    - The agent is told to avoid guessing when data is missing.
    |

    Text DetailsData Source Description
    Dataset includes Sales, Product (toys), Customer, and DimDate.

    Sales is the main fact table and is connected to Product, Customer,
    and DimDate via foreign keys.

    Data Source Instructions
    Use Sales for revenue and quantity.
    Join Product for toy names.
    Use DimDate for filtering by time.
    Do not expose customer emails.

    Included in this step:
    - Main dataset entities and relationships are clarified.
    - Usage rules are added for joins and time filtering.
    - Privacy guidance is added to avoid exposing customer emails.
    | +|

    Text DetailsYou are a Microsoft Fabric Data Agent.
    Answer business questions using only the connected tables.

    Consider the data schema:
    - Sales contains transactional data.
    - Product represents toys.
    - Customer contains customer attributes.
    - DimDate must be used for all time-based filtering.

    Avoid guessing when data is not available.

    Included in this step:
    - Role and scope are enforced for connected tables only.
    - Schema-aware guidance is added (Sales, Product, Customer, DimDate).
    - The agent is told to avoid guessing when data is missing.
    |

    Text DetailsData Source Description
    Dataset includes Sales, Product (toys), Customer, and DimDate.

    Sales is the main fact table and is connected to Product, Customer,
    and DimDate via foreign keys.

    Data Source Instructions
    Use Sales for revenue and quantity.
    Join Product for toy names.
    Use DimDate for filtering by time.
    Do not expose customer emails.

    Included in this step:
    - Main dataset entities and relationships are clarified.
    - Usage rules are added for joins and time filtering.
    - Privacy guidance is added to avoid exposing customer emails.
    | **Data Agent Answer** - + What to notice: - The answer clearly separates sales results and satisfaction results. - It explicitly flags missing rating data for one top-sales month. @@ -162,10 +162,10 @@ What to notice: | Agent Instructions | Data Source Description and Instructions | |:--|:--| -|

    Text DetailsYou are a Microsoft Fabric Data Agent acting as a data analyst.
    Before answering:
    - Identify the metric (Revenue, Quantity, Orders, Avg Rating).
    - Identify the grain (Product, Customer, State, Month, etc.).
    - Identify required filters (date, geography, category).
    Always:
    - Use Sales for performance metrics.
    - Use Product.ProductName as the toy name.
    - Use DimDate for all time logic.
    - Explain clearly if requested data is not available.

    Included in this step:
    - Guardrails for behavior and wording are strengthened.
    - The agent is guided to avoid over-claiming. |

    Text DetailsData Source Description
    The dataset follows a star-schema-like structure.

    Sales is the central fact table.
    Dimensions include Product (toys), Customer, State/Region, and DimDate.
    Feedback-related tables are used only for ratings and reviews.

    Data Source Instructions
    - Revenue = SUM(Sales.TotalPrice)
    - Quantity = SUM(Sales.Quantity)
    - Orders = COUNT(DISTINCT Sales.OrderNumber)

    Join rules:
    Sales -> Product via ProductID
    Sales -> DimDate via OrderDate

    Avoid PII fields unless explicitly requested.

    Included in this step:
    - Routing and usage guidance is expanded.
    - More explicit rules are added for safer interpretation. | +|

    Text DetailsYou are a Microsoft Fabric Data Agent acting as a data analyst.
    Before answering:
    - Identify the metric (Revenue, Quantity, Orders, Avg Rating).
    - Identify the grain (Product, Customer, State, Month, etc.).
    - Identify required filters (date, geography, category).
    Always:
    - Use Sales for performance metrics.
    - Use Product.ProductName as the toy name.
    - Use DimDate for all time logic.
    - Explain clearly if requested data is not available.

    Included in this step:
    - Guardrails for behavior and wording are strengthened.
    - The agent is guided to avoid over-claiming. |

    Text DetailsData Source Description
    The dataset follows a star-schema-like structure.

    Sales is the central fact table.
    Dimensions include Product (toys), Customer, State/Region, and DimDate.
    Feedback-related tables are used only for ratings and reviews.

    Data Source Instructions
    - Revenue = SUM(Sales.TotalPrice)
    - Quantity = SUM(Sales.Quantity)
    - Orders = COUNT(DISTINCT Sales.OrderNumber)

    Join rules:
    Sales -> Product via ProductID
    Sales -> DimDate via OrderDate

    Avoid PII fields unless explicitly requested.

    Included in this step:
    - Routing and usage guidance is expanded.
    - More explicit rules are added for safer interpretation. | **Data Agent Answer** - + What to notice: - The response is concise and avoids over-claiming when feedback data is missing. - It gives a clear summary of what is known vs unknown. @@ -175,10 +175,10 @@ What to notice: | Agent Instructions | Data Source Description and Instructions | |:--|:--| -|

    Included in this step:
    - Full planning, validation, and response guardrails are defined.
    - Behavior is tuned for consistency under complex prompts. |

    Included in this step:
    - Data model and usage rules are comprehensive.
    - Time logic, routing, and safety constraints are explicit. | +|

    Included in this step:
    - Full planning, validation, and response guardrails are defined.
    - Behavior is tuned for consistency under complex prompts. |

    Included in this step:
    - Data model and usage rules are comprehensive.
    - Time logic, routing, and safety constraints are explicit. | **Data Agent Answer** - + What to notice: - The answer includes a direct comparison table for sales vs ratings. - Numeric evidence is clear and easy to verify. @@ -568,13 +568,13 @@ In this task, you will publish and open the Data Agent so it can be used for tes NarrativeNotes In the Data Agent editor, locate the Publish button in the top menu (1). Enable Also publish to the Agent Store in Microsoft 365 Copilot by turning the toggle On (2). Click Publish (3) to complete the publishing process.Once published, the agent moves from Draft to Published status. - + Open the Notifications panel from the top right corner (1). Verify the message "Successfully published data agent" appears (2).This confirms the agent was published without errors. - + Return to the Data Agent main page. In the top right status indicator (1), confirm: Status shows Published.A published version is available. The agent is now ready to be used for testing natural language questions, sharing with others, and integration with Copilot experiences. - + Open the Fabric Agent at m365.cloud.microsoft/chat. - +